Injecting Mockito mocks into a Spring bean

后端 未结 22 1178
庸人自扰
庸人自扰 2020-11-22 09:44

I would like to inject a Mockito mock object into a Spring (3+) bean for the purposes of unit testing with JUnit. My bean dependencies are currently injected by using the

相关标签:
22条回答
  • 2020-11-22 10:15

    I use a combination of the approach used in answer by Markus T and a simple helper implementation of ImportBeanDefinitionRegistrar that looks for a custom annotation (@MockedBeans) in which one can specify which classes are to be mocked. I believe that this approach results in a concise unit test with some of the boilerplate code related to mocking removed.

    Here's how a sample unit test looks with that approach:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(loader=AnnotationConfigContextLoader.class)
    public class ExampleServiceIntegrationTest {
    
        //our service under test, with mocked dependencies injected
        @Autowired
        ExampleService exampleService;
    
        //we can autowire mocked beans if we need to used them in tests
        @Autowired
        DependencyBeanA dependencyBeanA;
    
        @Test
        public void testSomeMethod() {
            ...
            exampleService.someMethod();
            ...
            verify(dependencyBeanA, times(1)).someDependencyMethod();
        }
    
        /**
         * Inner class configuration object for this test. Spring will read it thanks to
         * @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class.
         */
        @Configuration
        @Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration
        @MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked
        static class ContextConfiguration {
    
            @Bean
            public ExampleService exampleService() {
                return new ExampleService(); //our service under test
            }
        }
    }
    

    To make this happen you need to define two simple helper classes - custom annotation (@MockedBeans) and a custom ImportBeanDefinitionRegistrar implementation. @MockedBeans annotation definition needs to be annotated with @Import(CustomImportBeanDefinitionRegistrar.class) and the ImportBeanDefinitionRgistrar needs to add mocked beans definitions to the configuration in it's registerBeanDefinitions method.

    If you like the approach you can find sample implementations on my blogpost.

    0 讨论(0)
  • 2020-11-22 10:15

    Today I found out that a spring context where I declared a before the Mockito beans, was failing to load. After moving the AFTER the mocks, the app context was loaded successfully. Take care :)

    0 讨论(0)
  • 2020-11-22 10:17

    For the record, all my tests correctly work by just making the fixture lazy-initialized, e.g.:

    <bean id="fixture"
          class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer"
          lazy-init="true" /> <!-- To solve Mockito + Spring problems -->
    
    <bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" />
    
    <bean id="applicationMessageBus"
          class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="it.tidalwave.messagebus.MessageBus" />
    </bean>
    
    <bean class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="javax.servlet.ServletContext" />
    </bean>
    

    I suppose the rationale is the one Mattias explains here (at the bottom of the post), that a workaround is changing the order the beans are declared - lazy initialization is "sort of" having the fixture declared at the end.

    0 讨论(0)
  • 2020-11-22 10:22

    I can do the following using Mockito:

    <bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="com.abcd.StateMachine"/>
    </bean>
    
    0 讨论(0)
  • 2020-11-22 10:23

    Since 1.8.3 Mockito has @InjectMocks - this is incredibly useful. My JUnit tests are @RunWith the MockitoJUnitRunner and I build @Mock objects that satisfy all the dependencies for the class being tested, which are all injected when the private member is annotated with @InjectMocks.

    I @RunWith the SpringJUnit4Runner for integration tests only now.

    I will note that it does not seem to be able to inject List<T> in the same manner as Spring. It looks only for a Mock object that satisfies the List, and will not inject a list of Mock objects. The workaround for me was to use a @Spy against a manually instantiated list, and manually .add the mock object(s) to that list for unit testing. Maybe that was intentional, because it certainly forced me to pay close attention to what was being mocked together.

    0 讨论(0)
  • 2020-11-22 10:23

    I developed a solution based on the proposal of Kresimir Nesek. I added a new annotation @EnableMockedBean in order to make the code a bit cleaner and modular.

    @EnableMockedBean
    @SpringBootApplication
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes=MockedBeanTest.class)
    public class MockedBeanTest {
    
        @MockedBean
        private HelloWorldService helloWorldService;
    
        @Autowired
        private MiddleComponent middleComponent;
    
        @Test
        public void helloWorldIsCalledOnlyOnce() {
    
            middleComponent.getHelloMessage();
    
            // THEN HelloWorldService is called only once
            verify(helloWorldService, times(1)).getHelloMessage();
        }
    
    }
    

    I have written a post explaining it.

    0 讨论(0)
提交回复
热议问题