Fix No Qualifying Spring Bean Error For Spring Boot Tests

Last Updated:  August 27, 2021 | Published: December 2, 2020

When working for the first time with Spring, you might encounter several no qualifying bean exceptions when you try to start your application. The stack trace is quite long for such error scenarios and it might become frustrating as your application doesn't start. The same is true whenever your test works with a Spring Test Context. Most of the time, the root cause for this is easy to fix. With this article, I'm covering a set of common pitfalls of why Spring is unable to resolve a bean when writing tests for a Spring Boot application.

The following examples are using JUnit 5 together with Spring Boot > 2.2.0.RELEASE

The Spring Bean is not part of the sliced Spring Context

A common scenario is the following: We have a Spring MVC controller endpoint that has several collaborators. During runtime, Spring injects these collaborators and the endpoint can do its work.

Let's take the PublicController as a simplified example for this common use case:

When testing this controller, we can use @WebMvcTest to test the controller with MockMvc. In addition to this, we get a sliced Spring Context that contains only relevant Spring Beans for this test.

If we would now write the following test setup and try to inject both an instance of MockMvc and the UserService

… any test inside this test class will fail with the following (reduced) stack trace:

These stack traces can be very long and might be overwhelming especially if you are new to Spring and Spring Boot. What's important here is the NoSuchBeanDefinitionException which causes the UnsatisfiedDependencyException. During context initialization, Spring is trying to instantiate the PublicController.  As this controller has one collaborator (UserService), Spring tries to resolve this dependency by injecting it from its context.

But there is no available bean of type UserService inside the test context as we are using @WebMvcTest which only populates MVC components. This ignores populating any non-relevant @Service or @Component classes.

In the end, this whole chain results in an IllegalStateException as Spring is not able to load the ApplicationContext. This will fail our test before we can even execute any test logic.

When testing different slices of our application in isolation, we can fix this unresolved bean exception by providing a mocked bean.

This will place a bean of the UserService inside the Spring Test Context. However, this bean doesn't represent the actual completion and is a mocked version of it. Please note that this @MockBean is different from Mockito's @Mock.

We can also add a real bean to our context, which we'll see in the last section of this article.

No qualifying bean because of untriggered auto-configuration

Another common error scenario is that we expect a specific auto-configuration mechanism to trigger, but it doesn't for our test setup.

A good example is the auto-configuration of the WebTestClient or RestTestTemplate which only happens when we start the embedded Servlet container during a test.

Using @SpringBootTest without any further configuration will use a mocked Servlet environment. That's why the following test:

… fails as the WebTestclient is not resolvable:

The fix for this is simple:

If you are using IntelliJ IDEA, you'll also get hints before runtime if you'll be able to inject a specific bean for your test. This is quite helpful if we forget e.g. to use @Serivce or @Component on top of our classes.

However, there are also false-positives where IDEA thinks we are unable to autowire a bean during tests. This usually happens for scenarios where we programmatically register beans or for more advanced setups.

Spring Boot also provides several meta-annotations to enable auto-configuration explicitly:

  • @AutoConfigureTestDatabase auto-configures a DataSource using an embedded database by default
  • @AutoConfigureCache to auto-configure a test CacheManager
  • @AutoConfigureMockRestServiceService to auto-configure a MockRestServivceServer to test a RestTemplate usage
  • etc.

We are writing a unit test without a Spring Context

Let's take a look at the next possible pitfall when testing our application. This time we want to write a unit test to verify the UserService. This class has one collaborator (the EntityManager) and takes care of creating new users:

Now when it comes to testing, developers sometimes mix concepts of plain unit testing and writing tests with support from Spring and Spring Boot. This might end up in the following (wrong) test setup.

As we are writing our application with Spring Boot, one might think that we are able to inject our beans anywhere. This is not the case here. For the test above no Spring Test Context is created at all and the entityManager field is null. We also don't register the SpringExtension here that takes care of injecting the beans for our test.

The correct way in this example would be to only rely on tools that JUnit 5 and Mockito provide.

The import section on top of the class is a great indicator to verify that we are not using anything from Spring or Spring Boot for our unit tests.

In this context, it's also important to understand the difference between @Mock and @MockBean.

The Spring Boot main class defines logic and has collaborators

Sometimes projects define Spring Beans or startup logic inside the Spring Boot main entry point class:

The logic above will always be triggered whenever we use a Spring Boot test slice annotation like @WebMvcTest or @DataJpaTest. We would see failing web layer tests because Spring is unable to load the ApplicationContext as it can't resolve the DataSource bean. That's an overhead we can avoid and we should keep our Spring Boot entry point class as empty as possible.

We can outsource our bean definitions to dedicated @Configuration classes. Otherwise, all your test that load an ApplicationContext would have to satisfy the collaborators of your main class.

The same is true for any startup logic that we might want to define at this point.

Adding any Spring Bean to our Spring Test Context

We now saw how we can fix common pitfalls when Spring is unable to resolve a bean. Up until this point we used @MockBean to place a mocked version of a Spring Bean inside the Spring Test Context. However, there might be scenarios where we don't want a mocked instance and rather a real one. Let's see how we can achieve this.

For demonstration purposes, we'll enrich the PublicController and add an additional collaborator: MeterRegistry.

Coming back to the same test we already saw before, we now have to decide what to do with both collaborators. By default, they are not part of the Spring Test Context, as @WebMvcTest only populates relevant Spring MVC components (speak Spring Beans).

Mocking the collaborator with @MockBean is always a valid option, but this time we want to add a real MeterRegistry instance to our Spring Test Context.

To achieve this, we can make use of Spring's @TestConfiguration annotation and create a nested static class that defines beans. For our example, it's only one as we want to still mock the UserService bean.

With this setup, our test is now able to load the Spring Test Context and can inject all collaborators to our PublicController. This solution gives us a lot of control as we can decide for every collaborator to either mock it or provide the actual implementation.

We can even outsource this test configuration to a dedicated class …

… to reuse it for other tests with @Import:

This approach allows us to add the real Spring Beans to our test context and e.g. test our web layer in combination with its real collaborator. In general, we should still favor mocking any interaction to the outside (speak to a collaborator) whenever we want to test our classes in isolation.

Have fun fixing your unresolved (no qualifying) Spring Bean exceptions,

Philip

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Sign up for Our Mailing List And Get

    the Testing Java Applications ($9) Cheat Sheet for Free

    Testing Java Applications Cheat Sheet Cover
    >