Spring: Please ignore my mocks

If you are using the core Spring Framework (that is, not Spring Boot), then you may have encountered a problem where Spring doesn’t seem to completely ignore mocked beans in your tests: Perhaps Spring attempts to inject beans into them or run your @PostConstruct lifecycle methods. In this post I present that problem together with a solution for it.

If you are using Spring Boot and have this behavior. Then there is a big chance that you are holding it wrong. There is a note on how to hold it right for you too :).

I have created an example application on GitHub. That application contains all the code that you see here. It is based on Spring Boot 2.0 and Java 8.

The problem

When you mock a Spring bean in your non-Spring Boot tests, and if the mock is based on a class and not an interface, then Spring attempts to autowire any dependencies it may have. Spring also attempts to invoke any @PostConstruct initializers that it may have. But …​ you just want to mock that d**n bean, right?

Consider the following bean that we want to test (our SUT):

public class GreeterService {

  @Autowired
  private GreeterDao greeterDao;

  public String sayHello(String caller) {
    String greetingTemplate = greeterDao.findGreeting();
    return String.format(greetingTemplate, caller);
  }
}

And this bean that we want to mock in our test:

public class GreeterDao {

  @Autowired
  private AnnoyingBean annoyingBean;

  @PostConstruct
  private void explodeOnStartup() {
    throw new RuntimeException("Oh no !");
  }

  public String findGreeting() {
    return "Hello world, %s";
  }
}

Here is a test that attempts to mock it:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BadTestConfig.class)
public class ProblemWithoutSpringBootGreeterServiceTests {

  @Autowired
  private GreeterService greeterService;

  @Autowired
  private GreeterDao greeterDaoMock;

  @Test
  public void sayHello() {

    // Given
    when(greeterDaoMock.findGreeting()).thenReturn("Hola contigo, %s");

    // When
    String greeting = greeterService.sayHello("Duke");

    // Then
    assertThat(greeting).matches("Hola contigo, Duke");
  }

  @Configuration
  static class BadTestConfig {

    @Bean
    GreeterService greeterService() {
      return new GreeterService();
    }

    @Bean
    GreeterDao greeterDao() {
      return mock(GreeterDao.class);
    }
  }
}

This test cannot even start the application context: Spring emits an error stating that it cannot find a bean of type AnnoyingBean. Now what the heck is this? Clearly I just want to have a mock of my GreeterDao, so why is Spring attempting to inject AnnoyingBean?

Well, Spring attempts to inject it because the mock is based on the actual GreeterDao class and not some common AbcDao interface. And since the mock is based on the class, then by inheritance it also has the @Autowired member. And once my bean object (the mock) gets returned by BadTestConfig::greeterDao(), then Spring will attempt to initialise it: injecting any dependencies to other beans and run any @PostConstruct lifecycle methods.

So there it is: spring treats the mock as any other bean.

Solution: Spring Boot based code

If you are using Spring Boot and have these kind of problems, then it is very likely just because you are not creating the mocks using Spring Boots awesome @MockBean annotation:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {GreeterService.class, GreeterDao.class})
public class SpringBootPoweredGreeterServiceTests {

  @Autowired
  private GreeterService greeterService;

  @MockBean
  private GreeterDao greeterDaoMock;

  @Test
  public void sayHello() {

    // Given
    when(greeterDaoMock.findGreeting()).thenReturn("Hola contigo, %s");

    // When
    String greeting = greeterService.sayHello("Duke");

    // Then
    assertThat(greeting).matches("Hola contigo, Duke");
  }

}

The @MockBean injection here ensures that Spring Boot correctly produces a mock that doesn’t get post processed.

Spring Boot: simple as always :).

Solution: Core Spring Framework based code

The trick is here to ensure that Spring won’t post process your mock. It just happens to be, that FactoryBeans have that behaviour:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AwesomeTestConfig.class)
public class SolutionWithoutSpringBootGreeterServiceTests {

  @Autowired
  private GreeterService greeterService;

  @Autowired
  private GreeterDao greeterDaoMock;

  @Test
  public void sayHello() {

    // Given
    when(greeterDaoMock.findGreeting()).thenReturn("Hola contigo, %s");

    // When
    String greeting = greeterService.sayHello("Duke");

    // Then
    assertThat(greeting).matches("Hola contigo, Duke");
  }

  @Configuration
  static class AwesomeTestConfig {

    @Bean
    GreeterService greeterService() {
      return new GreeterService();
    }

    @Bean
    FactoryBean greeterDao() {
      return new AbstractFactoryBean() {
        @Override
        public Class getObjectType() {
          return GreeterDao.class;
        }

        @Override
        protected GreeterDao createInstance() {
          return mock(GreeterDao.class);
        }
      };
    }
  }
}

Notice how AwesomeTestConfig::greeterDao() returns a bean factory instead of the mock directly. This test runs without error.

If you are going to use this trick, then make sure that you hide this ugly functionality away. You could, for example, invent a utility method with a nice signature. For example: my.MockitoFactoryBean.create(Class clazzToMock).

The workaround here also applies to XML based configuration. Just ensure you have that nice utility method – and then use that to define you bean.

Spring: Bean qualification

In this post I present how you can control a situation in which there are multiple beans that qualifies for an injection point in Spring.

The material here has been tested with Spring Framework 4.3.

The problem

Let’s first take a look at the problem. Imagine you have this interface:

public interface BusinessEventLogger {
}

And two different implementations of this. First this:

@Repository
public class DatabaseBusinessEventLogger implements BusinessEventLogger {
}

And then this:

@Repository
public class WebServiceBusinessEventLogger implements BusinessEventLogger {
}

Now, when you try to inject by the interface…

@Service
public class GreeterService {

  @Autowired
  private BusinessEventLogger businessEventLogger;

  public String sayHello(String caller) {
    businessEventLogger.log("Sending hello message to %s", caller);
    return String.format("Hello World, %s", caller);
  }

}

… then Spring will fail with a NoUniqueBeanDefinitionException. That is because Spring cannot decide which repository implementation is appropriate. After all, there are two equally valid bean candidates here.

Techniques that you can use to get back in control:

  • Static decisions:
    • Use the bean class
    • Use the injection target member name
    • Use the @Qualifier annotation
    • Use a custom qualifier annotation
    • Use @Primary
  • Runtime decisions:
    • Use @Profile
    • Use @Profile and @Primary

That should be enough to get you started. Notice the grouping. Some of the techniques require you at development time to know which bean is relevant. Other techniques allow you to defer the decision until runtime. The latter group is appropriate if the actual decision is environment specific for example.

I have prepared an example on GitHub that shows the problem and each of the solutions. It is based on Spring Framework 4.3 (via Spring Boot 1.4.1). And it is a multi module Gradle project. The problem scenario itself is illustrated in module 00_TheProblem. The solution scenarios are illustrated in the modules named something similar to 0x_TheSolution_abc.

Solution 01: Use the bean class

This technique is applicable if you, at development time, already know the specific bean you need:

@Service
public class GreeterService {

  @Autowired
  private WebServiceBusinessEventLogger businessEventLogger;

  public String sayHello(String caller) {
    businessEventLogger.log("Sending hello message to %s", caller);
    return String.format("Hello World, %s", caller);
  }

}

Notice that the class WebServiceBusinessEventLogger has been hardcoded here.

Solution 02: Use the injection target member name

This technique is applicable if you, at development time, already know the specific bean you need:

@Service
public class GreeterService {

  @Autowired
  private BusinessEventLogger webServiceBusinessEventLogger;

  public String sayHello(String caller) {
    webServiceBusinessEventLogger.log("Sending hello message to %s", caller);
    return String.format("Hello World, %s", caller);
  }

}

Notice the name of the member field being injected into: webServiceBusinessEventLogger. This name happens to match the bean name of the bean that  is-a WebServiceBusinessEventLogger.

A bit fragile huh? One day another developer may drop by and refactor the name to something else. If that happens – then the application won’t start anymore.

Solution 03: Use @Qualifier

This technique is applicable if you, at development time, already know the specific bean you need:

@Service
public class GreeterService {

  @Autowired @Qualifier("webServiceBusinessEventLogger")
  private BusinessEventLogger businessEventLogger;

  public String sayHello(String caller) {
    businessEventLogger.log("Sending hello message to %s", caller);
    return String.format("Hello World, %s", caller);
  }

}

Notice the use of Spring’s @Qualifier annotation. It tells Spring exactly which bean is needed – by specifying its name.

I think this solution is more robust than solution 02. But it is still a bit fragile. What if somebody decides to refactor the name of the WebServiceBusinessEventLogger class?

Solution 04: Use a custom qualifier annotation

This technique is applicable if you, at development time, already know the specific bean you need:

@Service
public class GreeterService {

  @Autowired @WebServiceStrategy
  private BusinessEventLogger businessEventLogger;

  public String sayHello(String caller) {
    businessEventLogger.log("Sending hello message to %s", caller);
    return String.format("Hello World, %s", caller);
  }

}

Notice the use of the @WebServiceStrategy annotation. This is a custom qualifier annotation. It tells Spring what bean is used. To make this work though, you first have to define the annotation:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServiceStrategy {
}

(Notice the use of Spring’s @Qualifier here. Now it is being used as a meta-annotation)

And you will also have to add it to the bean:

@Component
@WebServiceStrategy
public class WebServiceBusinessEventLogger implements BusinessEventLogger {
}

You also need to do the same for the database logger.

This solution is as type safe as it gets. But at the expense of additional ceremony. It can make sense, if the abstraction of the qualifier annotation names is good enough.

Solution 05: Use @Primary

This technique is applicable if you, at development time, already know the specific bean you need. You put Spring’s @Primary annotation on the “primary” bean:

@Component
@Primary
public class WebServiceBusinessEventLogger implements BusinessEventLogger {
}

This will make the WebServiceBusinessEventLogger bean “win”.

Notice the difference from the previous solutions:

Using Spring’s @Primary annotation on a bean is a global decision: every injection point will get the chosen bean.

This may seem like a weird solution. And in production code – using it like this – perhaps it is. But this is not where @Primary shines. Rather:

The @Primary bean feature is interesting when used together with @Profile to activate the primary bean at runtime.

OR:

The @Primary bean feature is interesting when used for testing purposes.

If you haven’t thought about this before, then now is the time to read that again :).

For testing purposes you can use @Primary to make a test bean “win” over a business bean. Take a look at Baeldung’s article for further inspiration [1]. In that article @Primary is used with a @Bean factory method and @Profile to make a test bean have precedence over a business bean. @Profile for this scenario isn’t necessary – unless the test bean also gets picked up when you run the application normally. That is typically an IDE configuration issue: when your test *.class files are colocated with the normal business *.class files (I’ve seen that in Eclipse once or twice).

Solution 06: Use @Profile

This technique is applicable if you want to choose the bean at runtime.

If you are using Spring Boot you could, as an example, start the application with:

--spring.profiles.active=webservice 

… to activate the webservice profile. Or you could also do it from a test, like this:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("webservice")
public class GreeterServiceIntegrationTests {
}

Notice the use of @ActiveProfiles – that tells Spring to run the ApplicationContext with the webservice profile activated.

In order for this to work, you also need to tell the conflicting beans what profile they belong to. You use the @Profile annotation for that. Here’s how you would do it for the WebServiceBusinessEventLogger class:

@Component
@Profile("webservice")
public class WebServiceBusinessEventLogger implements BusinessEventLogger {
}

Notice @Profile. That annotation tells Spring to only add the bean to the ApplicationContext if the webservice profile is active. @ActiveProfiles is different: it’s a test specific annotation that you put on test classes to tell Spring what profiles to activate when running the tests.

Unless you do anything else: You still need to put the @Profile annotation on the database logger too. Otherwise there will still be a conflict when you run with the webservice profile active (since, now there are two active beans candidates). If you have more candidates – then you will have to put @Profile on them too.

Solution 07: Use @Profile and @Primary

This technique is applicable if you want to choose the bean at runtime. It is 90% similar to solution 06. But this solution doesn’t require you to put @Profile on all candidates – only on the one that you also choose to be @Primary:

@Component
@Profile("webservice")
@Primary
public class WebServiceBusinessEventLogger implements BusinessEventLogger {
}

That’s it. Nothing else. If you have more candidates – then you will have to put @Profile and @Primary on them too.

The difference to solution 06, is that in this solution, one bean acts as a default bean: namely the one without @Profile.

Conclusion

This post described different techniques to solving the situation where you have multiple bean candidates for an injection point. Some of them are appropriate if you can make the decision at development time. Others are appropriate if you would like the flexibility at runtime. All of them have been possible since Spring Framework 3.x.

I haven’t included:

  • XML examples
  • Standards based qualification examples (JSR330 standard annotations [2])
  • @Bean examples

They do provide some further alternatives to fulfil the mission. That is an exercise for the motivated reader :).

I would love to hear from you, if you believe that I have forgotten some other obvious techniques. Just leave a comment before you leave.

References

[1] Injecting Mockito Mocks into Spring Beans :
http://www.baeldung.com/injecting-mocks-in-spring

[2] What is the relation between JSR-299 and JSR-330 in Java EE 6? Do we need two DI APIS?:
http://www.adam-bien.com/roller/abien/entry/what_is_the_relation_between