jmockit / jmockit1

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

Injection fails in abstract classes for a private field hidden by a field of the same name in sub-classes. #197

Closed glavoie closed 9 years ago

glavoie commented 9 years ago

Hi, currently trying to use injectable mocks with Spring services. I found out that if an abstract class injects a service into a private field and that a field of the same name exists in a sub-class, a mock isn't injected correctly into the abstract class. NullPointerException occurs when trying to access the field.

@Service public class InjectableClass { public String getHello() { return "Hello"; }

public String getBye() {
    return "Bye";
}

}

public abstract class AbstractWithInjection { @Inject private InjectableClass injectableClass;

public String hello() {
    return injectableClass.getHello();
}

public abstract String bye();

}

@Service public class InheritedWithInjection extends AbstractWithInjection { @Inject private InjectableClass injectableClass;

public String bye() {
    return injectableClass.getBye();
}

}

public class JmockitInjectionIssueTest { @Injectable private InjectableClass injectableClass;

@Tested
private InheritedWithInjection inheritedWithInjection;

@Test
public void shouldPassButFailsWithNullPointerException() {
    new Expectations() {
        {
            injectableClass.getHello();
            result = "Foo";
        }
    };

    assertThat(inheritedWithInjection.hello()).isEqualTo("Foo");
}

@Test
public void passesAsExpected() {
    new Expectations() {
        {
            injectableClass.getBye();
            result = "Bar";
        }
    };

    assertThat(inheritedWithInjection.bye()).isEqualTo("Bar");
}

}

rliesenfeld commented 9 years ago

After examining this issue, I came to the conclusion it's best to leave it as is.

The current behavior is consistent with the API documentation for @Tested and @Injectable, which basically say that only a still unused injectable can be injected into a field in a tested object. So, once the injectable is injected into the first field in the tested object (where fields in the subclass are given precedence), it won't be considered for further injection into the same tested object.

The existence of two or more fields of the same type and name in a class hierarchy is widely regarded as a design issue; Java IDEs and static code analysys tools such as FindBugs will promptly show a warning when they detect a field hiding another. So, the correct solution is to either remove or rename one of the fields. Also, note that if JMockit were to inject the same injectable into two separate fields, a test would not be able to specify different behavior for them; with two fields of different names, tests can use separate injectables.