67

Fix No Qualifying Spring Bean Error For Spring Boot Tests

 3 years ago
source link: https://rieckpil.de/fix-no-qualifying-spring-bean-error-for-spring-boot-tests/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Fix No Qualifying Spring Bean Error For Spring Boot Tests

December 2, 2020

First time here? Get an overview of all topics you'll find answers for on this blog here.

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 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:

@RestController
@RequestMapping("/public")
public class PublicController {
  private final UserService userService;
  public PublicController(UserService userService) {
    this.userService = userService;
  // ... endpoint definitions

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

@WebMvcTest(PublicController.class)
class PublicControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @Autowired
  private UserService userService;
  // ... tests

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

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
    Error creating bean with name 'publicController' defined in file [/home/rieckpil/development/git/sample-project/target/classes/de/rieckpil/blog/PublicController.class]:
    Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
    No qualifying bean of type 'de.rieckpil.learning.UserService' available:
    expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    ... 68 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
    No qualifying bean of type 'de.rieckpil.learning.UserService' available:
    expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    ... 87 more

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.

@WebMvcTest(PublicController.class)
class PublicControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @MockBean
  private UserService userService;
  // ...

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:

@SpringBootTest
class ApplicationIT {
  @Autowired
  private WebTestClient webTestClient;
  @Test
  void shouldReturn200ForPublicEndpoint() {
    // ...

… fails as the WebTestclient is not resolvable:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'de.rieckpil.blog.ApplicationIT':
  Unsatisfied dependency expressed through field 'webTestClient';
  nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
  No qualifying bean of type 'org.springframework.test.web.reactive.server.WebTestClient' available:
  expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
  {@org.springframework.beans.factory.annotation.Autowired(required=true)}

The fix for this is simple:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ApplicationIT {
  @Autowired
  private WebTestClient webTestClient;

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

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:

@Service
public class UserService {
  private final EntityManager entityManager;
  public UserService(EntityManager entityManager) {
    this.entityManager = entityManager;
  public void create(String username) {
    // create the user

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.

// wrong test setup, won't work
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
  @Autowired
  private EntityManager entityManager;
  private UserService userService;
  @Test
  void shouldCreateUser() {
    this.userService = new UserService(entityManager);
    // possible NullPointerExceptions as the entityManager is null
    this.userService.create("duke");

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.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.EntityManager;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
  @Mock
  private EntityManager entityManager;
  @InjectMocks
  private UserService userService;
  @Test
  void shouldCreateUser() {
    // test logic

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:

@SpringBootApplication
public class Application implements CommandLineRunner {
  private final DataSource dataSource;
  public Application(DataSource dataSource) {
    this.dataSource = dataSource;
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  @Override
  public void run(String... args) throws Exception {
    System.out.println("Connection to database successful: "
      + dataSource.getConnection().getMetaData().getDatabaseProductName());

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.

@RestController
@RequestMapping("/public")
public class PublicController {
  private final UserService userService;
  private final MeterRegistry meterRegistry;
  public PublicController(UserService userService, MeterRegistry meterRegistry) {
    this.userService = userService;
    this.meterRegistry = meterRegistry;
  // endpoint definitions

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.

@WebMvcTest(PublicController.class)
class PublicControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @Autowired
  private MeterRegistry meterRegistry;
  @MockBean
  private UserService userService;
  @TestConfiguration
  static class TestConfig {
    @Bean
    public MeterRegistry meterRegistry() {
      return new SimpleMeterRegistry();

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 …

@TestConfiguration
public class DefaultRegistryConfig {
  @Bean
  public MeterRegistry meterRegistry() {
    return new SimpleMeterRegistry();

… to reuse it for other tests with @Import:

@WebMvcTest(PublicController.class)
@Import(DefaultRegistryConfig.class)
class PublicControllerTest {
  // no nested @TestConfiguration class needed and we can reuse it

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

Want to learn more about testing? Join the Testing Java Applications Email Course

  • 14 Days Free E-Mail Course
  • Independent of the application framework
  • All participants receive the JUnit 5 & Mockito Cheat Sheet ($ 9.99)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK