jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
461 stars 239 forks source link

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

Closed jamesflagg closed 10 years ago

jamesflagg commented 10 years ago

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 commented 10 years ago

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 commented 10 years ago

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

jamesflagg commented 10 years ago

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.