Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4537

Using @Autowired and @InjectMocks in Spring Boot Tests

$
0
0

1. Overview

In this tutorial, we’ll explore the usage of Spring Boot’s @Autowired and Mockito’s @InjectMocks while injecting dependencies in Spring Boot tests. We’ll go over the use cases that require us to use them and look at examples for the same.

2. Understanding Test Annotations

Before starting with the code example, let’s quickly look at the basics of some test annotations.

First, the most commonly used @Mock annotation of Mockito creates a mock instance of a dependency for testing. It’s often used in conjunction with @InjectMocks which injects the mocks marked with @Mock into the target object being tested.

In addition to Mockito’s annotations, Spring Boot’s annotation @MockBean can help create a mocked Spring bean. The mocked bean can then be used by other beans in the context. Moreover, if a Spring context creates beans on its own that can be utilized without mocking, we can use the @Autowired annotation to inject them.

3. Example Setup

In our code example, we’ll create a service having two dependencies. We’ll then explore using the above annotations to test the service.

3.1. Dependencies

Let’s start by adding the required dependencies. We’ll include the Spring Boot Starter Web and Spring Boot Starter Test dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.2.5</version>
    <scope>test</scope>
</dependency>

In addition to this, we’ll add the Mockito Core dependency that we’ll need to mock our services:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
</dependency>

3.2. DTO

Next, let’s create a DTO that we’ll use in our services:

public class Book {
    private String id;
    private String name;
    private String author;
    
    // constructor, setters/getters
}

3.3. Services

Next, let’s look at our services. First, let’s define a service that is responsible for database interactions:

@Service
public class DatabaseService {
    public Book findById(String id) {
        // querying a Database and getting a book
        return new Book("id","Name", "Author");
    }
}

We’ll not go into the database interactions as they are irrelevant to the example. We use the @Service annotation to declare the class a Spring bean of Service stereotype.

Next, let’s introduce a service that is dependent on the above service:

@Service
public class BookService {
    private DatabaseService databaseService;
    private ObjectMapper objectMapper;
    BookService(DatabaseService databaseService, ObjectMapper objectMapper) {
        this.databaseService = databaseService;
        this.objectMapper = objectMapper;
    }
    String getBook(String id) throws JsonProcessingException {
        Book book = databaseService.findById(id);
        return objectMapper.writeValueAsString(book);
    }
}

Here we have a small service that has a getBook() method. The method utilizes the DatabaseService to get a book from the database. It then uses the ObjectMapper API of Jackson to convert and return the Book object into a JSON string.

Therefore, this service has two dependencies: DatabaseService and ObjectMapper.

4. Testing

Now that our services are set up, let’s look at ways to test BookService using the annotations we defined earlier.

4.1. Using @Mock and @InjectMocks

The first option is to mock both dependencies of the service using @Mock and inject them into the service using @InjectMocks. Let’s create a test class for the same:

@SpringBootTest
class BookServiceMockAndInjectMocksUnitTest {
    @Mock
    private DatabaseService databaseService;
    @Mock
    private ObjectMapper objectMapper;
    @InjectMocks
    private BookService bookService;
    @Test
    void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
        Book book1 = new Book("1234", "Inferno", "Dan Brown");
        when(databaseService.findById(eq("1234"))).thenReturn(book1);
        when(objectMapper.writeValueAsString(any())).thenReturn(new ObjectMapper().writeValueAsString(book1));
        String bookString1 = bookService.getBook("1234");
        Assertions.assertTrue(bookString1.contains("Dan Brown"));
    }
}

First, we annotate the test class with @SpringBootTest. This signifies that the application context will be loaded before running the test. This is required when replacing Spring bean dependencies using @InjectMocks.

Next, we declare the DatabaseService and ObjectMapper fields and annotate them with @Mock. This creates mocked objects for both of them. We add the @InjectMocks annotation when declaring our BookService instance that we’ll test. This injects any dependencies that the service requires and have been declared earlier with @Mocks.

Finally, in our test, we mock the behavior of our mocked objects and test the getBook() method of our service.

It’s mandatory to mock all the dependencies of the service when using this method. For example, if we don’t mock ObjectMapper, it leads to a NullPointerException when it’s called in the tested method.

4.2. Using @Autowired With @MockBean

In the above method, we mocked both the dependencies. However, it may be required to mock some of the dependencies and not mock the others. Let’s assume we don’t need to mock the behavior of ObjectMapper and mock only DatabaseService.

Since we’re loading the Spring context in our test, we can use the combination of @Autowired and @MockBean annotations to do so:

@MockBean
private DatabaseService databaseService;
@Autowired
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
    Book book1 = new Book("1234", "Inferno", "Dan Brown");
    when(databaseService.findById(eq("1234"))).thenReturn(book1);
    String bookString1 = bookService.getBook("1234");
    Assertions.assertTrue(bookString1.contains("Dan Brown"));
}

We annotate DatabaseService with @MockBean. Then we get the BookService instance from the application context using @Autowired.

When the BookService bean is injected, the actual DatabaseService bean will be replaced by the mocked bean. Conversely, the ObjectMapper bean remains the same as originally created by the application.

When we test this instance now, we don’t need to mock any behavior for ObjectMapper.

This method is useful when we need to test the behavior of nested beans and don’t want to mock every dependency.

4.3. Using @Autowired and @InjectMocks Together

We could also use @InjectMocks instead of @MockBean for the above use case.

Let’s look at the code to see the difference between the two methods:

@Mock
private DatabaseService databaseService;
@Autowired
@InjectMocks
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
    Book book1 = new Book("1234", "Inferno", "Dan Brown");
    MockitoAnnotations.openMocks(this);
    when(databaseService.findById(eq("1234"))).thenReturn(book1);
    String bookString1 = bookService.getBook("1234");
    Assertions.assertTrue(bookString1.contains("Dan Brown"));
}

Here, we mock the DatabaseService using @Mock instead of @MockBean. In addition to @Autowired, we add the @InjectMocks annotation to the BookService instance.

When both annotations are used together, @InjectMocks doesn’t inject the mocked dependency automatically and the auto-wired BookService object is injected when the test starts.

However, we can inject the mocked instance of DatabaseService later in our test by calling the MockitoAnnotations.openMocks() method. This method looks for fields marked with @InjectMocks and injects mocked objects into it.

We call it in our test just before we need to mock the behavior of DatabaseService. This method is useful when we want to dynamically decide when to use mocks and when to use the actual bean for the dependency.

5. Comparison of Approaches

Now that we’ve looked at multiple approaches, let’s summarise a comparison between them:

Approach Description Usage
@Mock with @InjectMocks Uses Mockito’s @Mock annotation to create a mock instance of a dependency and @InjectMocks to inject these mocks into the target object being tested. Suitable for unit testing where we want to mock all dependencies of the class under test.
@MockBean with @Autowired Utilizes Spring Boot’s @MockBean annotation to create a mocked Spring bean and @Autowired to inject these beans. Ideal for integration testing in Spring Boot applications. It allows mocking some Spring beans while getting the other beans from Spring’s dependency injection.
@InjectMocks with @Autowired  Uses Mockito’s @Mock annotation to create mock instances, and @InjectMocks to inject these mocks into target beans already auto-wired using Spring. Provides flexibility in scenarios where we need to mock some dependencies temporarily using Mockito to override injected Spring Beans. Useful for testing complex scenarios in Spring applications.

6. Conclusion

In this article, we looked at different use cases of Mockito and Spring Boot annotations – @Mock, @InjectMocks, @Autowired and @MockBean. We explored when to use different combinations of the annotations as per our testing needs.

As usual, the code examples for this tutorial are available over on GitHub.

       

Viewing all articles
Browse latest Browse all 4537

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>