22

I want configure a component test using spring-test configuration inner class (@Configuration). Tested components has some services which I'd like to mock for the test. These services are classes (no interface used) and have spring annotations (@Autowired) in them. Mockito can easily mock them, however, I found no way of disabling spring autowiring.

Example how I can easily reproduce:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {

    // configured in component-config.xml, using ThirdPartyService
    @Autowired
    private TestedBean entryPoint;

    @Test
    public void test() {
    }

    @Configuration
    @ImportResource("/spring/component-config.xml")
    static class Beans {
        @Bean
        ThirdPartyService createThirdPartyService() {
            return mock(ThirdPartyService.class);
        }
    }
}

public class ThirdPartyService {
    @Autowired
    Foo bar;
}

public class TestedBean {
    @Autowired
    private ThirdPartyService service;
}

In this example "TestBean" represents the service to be mocked. I would NOT like "bar" to be injected by spring! @Bean(autowire = NO) does not help (in fact, that's the default value). (Please save me from "use interfaces!" comments - the mocked service can be 3rd party which I can't do anything with.)

UPDATE

Springockito partially solves the problem, as long as you don't have to have anything else to configure (so you can't use configuration class with Springockito - it does not support it), but use mocks only. Still looking for pure spring solution, if there's any...

Xstian
  • 8,184
  • 10
  • 42
  • 72
vuk
  • 501
  • 1
  • 5
  • 9
  • Can you please show some more details of what you are trying to test? The best solution depends highly on what you actually want to test – geoand Sep 24 '14 at 12:01
  • I am not sure that you have to mock the dependencies of other mock. At least, it breaks my unit test world. – walv Sep 24 '14 at 12:15
  • @geoand I updated the example slightly. I want to test a bean configured in main scope, but context has to be mocked partly in test scope to make it work. Problem is that mocked class has Autowired annotation, and injecting into it can't be switched off. – vuk Sep 24 '14 at 12:18
  • @walv that's the point .. I don't want to most the dependency of the mock, but spring kinda forces me to. – vuk Sep 24 '14 at 12:19
  • @vuk Do you have the ability to refactor `ThirdPartyService`? – geoand Sep 24 '14 at 12:24
  • Theoretically no (if it's 3rdparty). However, let's assume I can. What would be your suggestion? (except extracting interface - that's the obvious solution) – vuk Sep 24 '14 at 12:25
  • I would refactor `ThirdPartyService` to have `bar` as a constructor dependency while removing `@Autowired` from the property. That way `AutowiredAnnotationBeanPostProcessor` (which takes care of the autowiring) would not try to wire the property. What do you think? – geoand Sep 24 '14 at 12:28
  • I think that refactoring a class in sake of testing is never a good idea. Tests must always adopt. And it still does not solve the case when you don't have the ability to touch the mocked class. – vuk Sep 24 '14 at 13:04
  • 2
    @vuk That's a matter of opinion. However I personally think that using `@Autowired` on fields is never a good idea. It kills reuse like in the case you specify. So in this case it's not a matter of tests adapting, it's a matter of making components easily reusable, whatever the context. One more involved solution to your problem is to register a custom `AutowiredAnnotationBeanPostProcessor` that will ignore the fields that you want. You woud combine that with Spring profiles in order to enable it only for testing. I can add it as an answer with more info if you would like to explore further – geoand Sep 24 '14 at 13:45

6 Answers6

19

Here is my solution to your problem:

import static org.mockito.Mockito.mockingDetails;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MockitoSkipAutowireConfiguration {

@Bean MockBeanFactory mockBeanFactory() {
    return new MockBeanFactory();
}

private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return !mockingDetails(bean).isMock();
    }
}

} 

and then just

@Import(MockitoSkipAutowireConfiguration.class)

in your test @Configuration and you are all set

potame
  • 7,597
  • 4
  • 26
  • 33
Paweł Kaczorowski
  • 1,502
  • 2
  • 14
  • 25
  • I'm not in a situation I could test this, but thanks! I'll let you know once I tested. – vuk Oct 16 '15 at 12:46
  • I had the same problem and this works perfectly well, thanks! This answer should be accepted – YingYang Feb 23 '16 at 10:37
  • This should be the accepted answer, works like a charm. – NagyI Jul 13 '16 at 14:52
  • If you have very old Mockito version < 1.9.5. You should use !(new MockUtil()).isMock(bean) instead of !mockingDetails(bean).isMock() which does exactly the same thing. – Sergey Sargsyan Feb 14 '18 at 11:11
  • This works wonderfully for a bunch of stuff where you need a class but don't want to mock out 20 dependencies to satisfy something that won't get tested/touched. Thanks! – Charles Hasegawa Aug 24 '21 at 16:42
6

I solved it by creating FactoryBean for my bean instead of just mocking bean. At this way Spring don't try to autowire fields.

Factory bean helping class:

public class MockitoFactoryBean<T> implements FactoryBean<T> {
    private final Class<T> clazz;

    public MockitoFactoryBean(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override public T getObject() throws Exception {
        return mock(clazz);
    }

    @Override public Class<T> getObjectType() {
        return clazz;
    }

    @Override public boolean isSingleton() {
        return true;
    }
}

Actual test context part:

@Configuration
public class TestContext {

    @Bean
    public FactoryBean<MockingService> mockingService() {
        return new MockitoFactoryBean<>(MockingService.class);
    }
}
tr1cks
  • 97
  • 2
  • 7
  • There is even a suitable "MockitoFactoryBean" class ready-written in the Springockito project: https://github.com/springockito/springockito/blob/master/springockito/src/main/java/org/kubek2k/mockito/spring/factory/MockFactoryBean.java – Rich Jun 12 '17 at 13:03
4

Check Spring profiles. You don't need to disable auto wiring, you need to inject different beans for different configuration.

Adisesha
  • 5,200
  • 1
  • 32
  • 43
  • How exactly will I be able to inject a mock into the context by using profiles (and also avoid spring autowiring into the mock - as this is the main issue here)?... Can you show example, please? – vuk Sep 24 '14 at 12:28
  • +1 nice answer.. [Here](http://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/) a simple example of spring profile. – Xstian Sep 24 '14 at 12:32
  • http://stackoverflow.com/questions/19808326/how-to-inject-a-mock-in-a-spring-context – Adisesha Sep 24 '14 at 12:34
  • @Adi According to that, there's NO solution by using spring only. Is that right ?.. – vuk Sep 24 '14 at 12:46
  • Follow the link in last answer(pointed to duplicates). The answers are mix of spring and Mockito. – Adisesha Sep 24 '14 at 12:52
  • @Adi .. okay, so there is NO solution with pure Spring usage, is there? Autowiring in unit tests can NOT be disabled. Is that what you're saying?. – vuk Sep 24 '14 at 12:54
  • Springockito says - _java.lang.IllegalStateException: Test class [SomeTest] has been configured with @ContextConfiguration's 'classes' attribute {class SomeTest$Beans}, but SpringockitoContextLoader does not support annotated classes._ I understand I could use test xml, but I'd also like to keep my test together, and if there's anything else (apart from mocks) I'd like to configure for my test, I will not be able to do that... And it's still not pure Spring solution. – vuk Sep 24 '14 at 12:59
2

I am in quite the same situation.

What I found that if you do not set the context loader by @ContextConfiguration annotation on your test class, the default context loader will be used, which derived from AbstractGenericContextLoader. I had a look at its source and turned out it registers all the bean post processors which are responsible for reading annotations such @Autowired. In other words, annotation config is enabled by default.

So the main problem is that there are two configurations which are in conflict: in the java config we said that autowiring is not needed, while the autowired annotation tells the opposite. The real question is how to disable the annotation processing in order to eliminate the undesired configuration.

As far as I know there is no such spring implementation of ContextLoader which would not be derived from AbstractGenericContextLoader so I guess the only we can do is to write our own. It would be something like this:

public static class SimpleContextLoader implements ContextLoader {

    @Override
    public String[] processLocations(Class<?> type, String... locations) {
        return strings;
    }

    @Override
    public ApplicationContext loadContext(String... locations) throws Exception {
        // in case of xml configuration
        return new ClassPathXmlApplicationContext(strings);
        // in case of java configuration (but its name is quite misleading)
        // return new AnnotationConfigApplicationContext(TestConfig.class);
    }

}

Of course it would be worth to spend more time to find out how to implement ContextLoader properly.

Cheers,
Robert

Robert
  • 21
  • 2
2

You could add the mocked service manually to the spring application context via org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton. This way the mock is not post-processed by spring and spring does not attempt to autowire the mock. The mock itself will be injected into your tested bean.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {

    // configured in component-config.xml, using ThirdPartyService
    @Autowired
    private TestedBean entryPoint;

    @Autowired
    private ThirdPartyService thirdPartyServiceMock;

    @Test
    public void test() {
    }

    @Configuration
    static class Beans {

        @Autowired
        private GenericApplicationContext ctx;

        @Bean
        TestedBean testedBean() {
            ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class));
            return new TestedBean();
        }
    }

    public static class ThirdPartyService {
        @Autowired
        Object bar;
    }

    public static class TestedBean {
        @Autowired
        private ThirdPartyService service;

    }
}
Oliver
  • 21
  • 2
  • I was looking for something more elegant .. Also, I am expecting my tested class declared in an xml (including from main part of the code). However, I definitely can do this registering in a \@PostConstruct part of either the \@Configuration class or the test class. – vuk Apr 09 '15 at 14:15
0

There are so many ways of doing this, I'm pretty sure that this answer will be incomplete, but here are a few options...

As currently seems to be recommended practice, use constructor injection for your services rather than autowiring the fields directly. This makes testing like this so much easier.

public class SomeTest {

    @Mock
    private ThirdPartyService mockedBean;

    @Before
    public void init() {
        initMocks(this);
    }

    @Test
    public void test() {
        BeanUnderTest bean = new BeanUnderTest(mockedBean);
        // ...
    }

}

public class BeanUnderTest{
    private ThirdPartyService service;
    @Autowired
    public BeanUnderTest(ThirdPartyService ThirdPartyService) {
        this.thirdPartyService = thirdPartyService;
    }
}

By doing that, you can also mix up autowired and mocked services by autowiring into the test itself and then constructing the beans under test with the most useful mix of autowired and mocked beans.

A reasonable alternative is to use Spring profiles to define stub services. This is particularly useful when wish to use the same stubbed features in multiple tests:

@Service
@Primary
@Profile("test")
public class MyServiceStub implements MyService {
    // ...
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class) 
@ActiveProfiles({"test"})
public class SomeTest {
    // ...
}

By using the @Primary annotation, it ensures that this stub bean will be used instead of any other bean implementing the MyService interface. I tend to use this approach for things like email services, where by changing profile, I'm able to switch between a real mail server and Wiser.

Steve
  • 9,270
  • 5
  • 47
  • 61
  • In your first example there are no Spring configuration and has nothing to do with my issue (how will the mock get injected into the context?). The wrapper does not help if context has to have a ThirdPartyService instance. In the second.. I know stubs, but not sure that will help (I want mocks to change their behaviour for the sake of the test). Also, in this example profile is obsolote, @Primary alone will do the trick if stubs are in test scope (and why wouldn't they be?). – vuk Sep 24 '14 at 14:40
  • You didn't say that you wanted a mock injected into the context. You don't actually need a Spring context for the example. If you want a mock/stub to be autowired into multiple beans by Spring, then something like the profiles-based approach is better. If you want to be able to change what it returns within individual tests, then write it that way rather than having it return fixed values.And just using @Primary is not a safe way of doing that. Far better to use profiles. – Steve Sep 24 '14 at 19:18
  • I thought the original example was quite clear. I've got a service configured in spring, and a mocked service injected into the configured one. Still nobody explained how profiling would solve mocks injected (especially with the original problem: without autowiring into them). I don't want stubs, I want mocks with in-line configured behaviour in test. – vuk Sep 25 '14 at 07:20
  • The best approach I can think of would be to go with the constructor injection approach and inject a mocked `ThirdPartyService`. Not having Spring create a `ThirdPartyService` bean and inject a mocked object into it. i.e. Keep your boundaries clean and test how your application interacts with that service, not whether that service works. – Steve Sep 25 '14 at 08:52
  • Otherwise as I mentioned in my answer, you can create an @Autowired field in your test class and replace the `Foo bar` which was injected with your mock. Of course this depends on whether the field is private or not and whether the third party provided setters or constructors to inject it. – Steve Sep 25 '14 at 08:54