Mocked class + Cascading class with similar name in same package = NoSuchMethodException #57

Closed
jamesflagg opened this Issue Sep 2, 2014 · 3 comments

Comments

2 participants
@jamesflagg

Use case:

  1. There is a class which is annotated as Mocked for a unit test.
  2. This class has a method which is recorded in an Expectations block.
  3. The recorded method returns an Object type (not void or primitive or String).
  4. We have a second class which happens to be in the same package as the first class, and whose name happens to start with the name of the first class.
  5. The second class is annotated as a Cascading mock for the test.

Result : When recording the expectations block, a NoSuchMethodException is thrown and causes a test failure.

@jamesflagg

This comment has been minimized.

Show comment
Hide comment
@jamesflagg

jamesflagg Sep 2, 2014

Full unit test to reproduce:

import mockit.Cascading;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import mockit.internal.state.ExecutingTest;

import org.junit.Test;

/**
 * This test reproduces a suspected JMockit bug in JMockit 1.11.
 * 
 * Use case:
 * <ol>
 * <li>We have a class which is Mocked for the unit test.
 * <li>This class has a method which is recorded in an Expectations block.
 * <li>The recorded method returns an Object type (not void or primitive or
 * String).
 * <li>We have a second class which happens to be in the same package as the
 * first class, and whose name happens to start with the name of the first
 * class.
 * <li>The second class is annotated as a Cascading mock for the test.
 * </ol>
 * 
 * <p>
 * Result: When recording the expectations block, a
 * {@link NoSuchMethodException} is thrown and causes a test failure.
 * 
 * <p>
 * The issue seems to originate in {@link ExecutingTest}, method
 * getCascade(String), line 354, where {@link String#startsWith(String)} is used
 * and which seems to pick up the wrong class, causing the problem.
 * 
 * @author James Flagg
 */
public class TestCascadingNameConflict {

    class Apple {

        // To reproduce this problem, the method to be mocked must return an
        // Object type, not void or a primitive type.
        public Slices slice() {

            // Real implementation doesn't matter; will be mocked below.
            return null;
        }
    }

    // A second class whose name just happens to begin with the name of the
    // first class.
    class Applesauce {

    }

    // Not important, but the mocked/recorded method must return an Object type
    class Slices {

    }

    @Mocked
    Apple apple;

    // This class, whose name begins with the name of the other Mocked class,
    // must be marked as Cascading to reproduce the problem. If it's just
    // "Mocked", then the test passes.
    @Cascading
    Applesauce applesauce;

    @Test
    public void myTest() {

        // The following expectations block causes:
        // java.lang.RuntimeException: java.lang.NoSuchMethodException:
        // TestCascadingNameConflict$Applesauce.slice()

        // Use of Expectations instead of NonStrictExpectations causes the same
        // problem.

        new NonStrictExpectations() {
            {
                apple.slice();
            }
        };
    }
}

Full unit test to reproduce:

import mockit.Cascading;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import mockit.internal.state.ExecutingTest;

import org.junit.Test;

/**
 * This test reproduces a suspected JMockit bug in JMockit 1.11.
 * 
 * Use case:
 * <ol>
 * <li>We have a class which is Mocked for the unit test.
 * <li>This class has a method which is recorded in an Expectations block.
 * <li>The recorded method returns an Object type (not void or primitive or
 * String).
 * <li>We have a second class which happens to be in the same package as the
 * first class, and whose name happens to start with the name of the first
 * class.
 * <li>The second class is annotated as a Cascading mock for the test.
 * </ol>
 * 
 * <p>
 * Result: When recording the expectations block, a
 * {@link NoSuchMethodException} is thrown and causes a test failure.
 * 
 * <p>
 * The issue seems to originate in {@link ExecutingTest}, method
 * getCascade(String), line 354, where {@link String#startsWith(String)} is used
 * and which seems to pick up the wrong class, causing the problem.
 * 
 * @author James Flagg
 */
public class TestCascadingNameConflict {

    class Apple {

        // To reproduce this problem, the method to be mocked must return an
        // Object type, not void or a primitive type.
        public Slices slice() {

            // Real implementation doesn't matter; will be mocked below.
            return null;
        }
    }

    // A second class whose name just happens to begin with the name of the
    // first class.
    class Applesauce {

    }

    // Not important, but the mocked/recorded method must return an Object type
    class Slices {

    }

    @Mocked
    Apple apple;

    // This class, whose name begins with the name of the other Mocked class,
    // must be marked as Cascading to reproduce the problem. If it's just
    // "Mocked", then the test passes.
    @Cascading
    Applesauce applesauce;

    @Test
    public void myTest() {

        // The following expectations block causes:
        // java.lang.RuntimeException: java.lang.NoSuchMethodException:
        // TestCascadingNameConflict$Applesauce.slice()

        // Use of Expectations instead of NonStrictExpectations causes the same
        // problem.

        new NonStrictExpectations() {
            {
                apple.slice();
            }
        };
    }
}

@rliesenfeld rliesenfeld added the bug label Sep 3, 2014

@rliesenfeld rliesenfeld self-assigned this Sep 3, 2014

@rliesenfeld

This comment has been minimized.

Show comment
Hide comment
@rliesenfeld

rliesenfeld Sep 3, 2014

Member

Thanks for the report; it's a weird bug, indeed.

Member

rliesenfeld commented Sep 3, 2014

Thanks for the report; it's a weird bug, indeed.

@jamesflagg

This comment has been minimized.

Show comment
Hide comment
@jamesflagg

jamesflagg Sep 3, 2014

Yes, perhaps a more realistic example is legacy code following a "value object" pattern, where two class names differ only by a suffix, e.g. "Employee" and "EmployeeVO". That's how I encountered it initially.

Yes, perhaps a more realistic example is legacy code following a "value object" pattern, where two class names differ only by a suffix, e.g. "Employee" and "EmployeeVO". That's how I encountered it initially.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment