I want to unit test a java class with an autowired final class object, as well as another autowired class that has @PostConstruct method. While it is possible to test them individually, i am not able to combine them together.
This question is an extension to the question on injecting mockito mocks into spring bean
Code to be tested
public class A {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//no setters or constructors
public String useBothFinalClassAndClassWithPostConstructor() {
//logic to be tested
}
}
Working Test class
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class ATest {
//@org.mockito.Mock //fails to mock final class
@org.powermock.api.easymock.annotation.Mock
private FinalClass serviceClient;
@org.powermock.api.easymock.annotation.Mock
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
//@InjectMocks //fails since mocking final class
private A a;
@Before
public void init() {
a = new A();
//working snippet with setters created in A and without @Autowired here within the test
serviceClient = PowerMock.create(FinalClass.class);
a.setServiceClient(serviceClient);
resourceVerifier = PowerMock.create(ClassWithPostConstructor.class);
a.setClassWithPostConstructor(resourceVerifier);
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functionality here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
The above code works with the need for setters in file
Expected Test class
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:spring-configuration/unit-testing-config.xml"})
@PrepareForTest(FinalClass.class)
public class ATest {
@Autowired
private FinalClass serviceClient;
@Autowired
private ClassWithPostConstructor resourceVerifier;
//other mock objects required for mocking the services
private A a;
@Before
public void init() {
a = new A();
}
@Test
public void testTheMethodUsingExpectAndVerify() {
//test the functions here
EasyMock.expect(serviceClient.callService()).andReturn("someMock");
EasyMock.expect(resourceVerifier.verifyFn()).andReturn("success");
PowerMock.replayAll();
A.useBothFinalClassAndClassWithPostConstructor();
PowerMock.verifyAll();
}
}
//spring-configuration/unit-testing-config.xml
//same error even on customer factory
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...resourceVerifier" />
</bean>
<bean id="resourceVerifier" class="org.powermock.api.easymock.PowerMock" factory-method="createMock">
<constructor-arg type="java.lang.Class" value="com.company...serviceClient" />
</bean>
The above snippet mocks finalClass but calls @PostConstructor of ResourceVerifier.class - What should be done here to overcome this call.
Investigations
- It is possible to test autowired files using @InjectMocks without the need for spring context configurations.
- @InjectMock fails silently for static and final fields and when failing, it doesn't inject other mocks as well.
- It is possible to mock final class using PowerMock's createMock and run the test with PowerMockRunner and @PrepareForTest. But this requires new unnecessary setters to inject the mocks for @Autowired fields.
- MockitoAnnotations.@Mock doesn't work along well with PowerMock (especially when mocking final class objects) and can be solved via EasyMock.Annotations.@Mock
- EasyMock and PowerMock doesn't have an @InjectMocks annotation to inject the mocks as possible by Mockito (Would have solved the problem in secs).
- It is possible to inject autowired spring beans via SpringJUnit4Runner and a separate unit-testing @ContextConfiguration
- It is possible to run the same test file with both PowerMockRunner and SpringJUnit4Runner with PowerMockRunnerDelegate
- I know that @PostConstruct method will not be executed automatically if mocked in code than by using spring bean creation and injection.
- If a wrapper factory bean class is written and used to create the mocks, it injects automatically, but calls the @PostConstruct method as well along with it.
- It is not possible to depend on Springockito since it is unreliable at this stage.
But none of these worked since the usecase is a combination of all these.
Possible Solutions
- Remove @Autowired fields and use Setter injections so that it is possible by mocking normally using PowerMock(Tested to work) - But it is a convention followed by external team packages - i should try my best to stick to it.
- Or set @Autowired to the setters or to constructor
Alternatives?
I don't feel that the classes require reorganisation as they serve their purposes and are well designed as well.
- Any other means that doesn't require to keep hands on the class under test - What if i didn't have the permissions to modify this class? i.e., a pure testing library dependent solution.
- Not sure whether it is possible by PowerMockito? Haven't tried the combination of PowerMockito with PowerMock.