简体   繁体   中英

Mock final class and inject it to autowired data member and overcome postConstruct method call

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? ie, a pure testing library dependent solution.
  • Not sure whether it is possible by PowerMockito ? Haven't tried the combination of PowerMockito with PowerMock .

Hmm,

Not sure whether it is possible by PowerMockito? Haven't tried the combination of PowerMockito with PowerMock.

Seems to me you have a mess in your head and misunderstanding PowerMock/Mockito and EasyMock.

You should never use at same time PowerMockito and PowerMock , because these two classes are PowerMock friendly API for two different mocking frameworks: EasyMock and Mockito . And there is no reason to use they both.

And of course this want work

//@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;

Because, you decelerate and create mocks via EasyMock API, but tries to inject it with Mockito Annotation.

You have to choose only one Mocking Framework and use suitable API for it. For now, Mockito + PowerMockito (PowerMock API for Mockito) better fit your requirements.

You may full example how it works on PowerMock github

@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class)
public class SpringInjectFinalClassExampleTest {

    @Mock
    private FinalClass finalClass;

    @InjectMocks
    private MyBean myBean = new MyBean();;

    @Test
    public void testInjectFinalClass() {
        final String value = "What's up?";
        when(finalClass.sayHello()).thenReturn(value);

        assertEquals(value, myBean.sayHello());

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM