9.2 C
New York
Tuesday, November 4, 2025
Array

Unit testing Spring MVC applications with JUnit 5



Here is the source code for the WidgetServiceTest class:

package com.infoworld.widgetservice.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

import java.util.Optional;

import com.infoworld.widgetservice.model.Widget;
import com.infoworld.widgetservice.repository.WidgetRepository;

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;

@ExtendWith(MockitoExtension.class)
public class WidgetServiceTest {
    @Mock
    private WidgetRepository repository;

    @InjectMocks
    private WidgetService service;

    @Test
    void testFindById() {
        Widget widget = new Widget(1L, "My Widget", 1);
        when(repository.findById(1L)).thenReturn(Optional.of(widget));

        Optional<Widget> w = service.findById(1L);
        assertTrue(w.isPresent());
        assertEquals(1L, w.get().getId());
        assertEquals("My Widget", w.get().getName());
        assertEquals(1, w.get().getVersion());
    }
}

JUnit 5 supports extensions and Mockito has defined a test extension that we can access through the @ExtendWith annotation. This extension allows Mockito to read our class, find objects to mock, and inject mocks into other classes. The WidgetServiceTest tells Mockito to create a mock WidgetRepository, by annotating it with the @Mock annotation, and then to inject that mock into the WidgetService, using the @InjectMocks annotation. The result is that we have a WidgetService that we can test and it will have a mock WidgetRepository that we can configure for our test cases.

Also see: Advanced unit testing with JUnit 5, Mockito, and Hamcrest.

This is not a comprehensive test, but it should get you started. It has a single method, testFindById(), that demonstrates how to test a service method. It creates a mock Widget instance and then uses the Mockito when() method, just as we used in the controller test, to configure the WidgetRepository to return an Optional of that Widget when its findById() method is called. Then it invokes the WidgetService’s findById() method and validates that the mock Widget is returned.

Slice testing a Spring Data JPA repository

Next, we’ll slice test our JPA repository (WidgetRepository.java), shown here:

package com.infoworld.widgetservice.repository;

import java.util.List;
import com.infoworld.widgetservice.model.Widget;
import org.springframework.data.jpa.repository.JpaRepository;

public interface WidgetRepository extends JpaRepository<Widget, Long> {
    List<Widget> findByName(String name);
}

The WidgetRepository is a Spring Data JPA repository, which means that we define the interface and Spring generates the implementation. It extends the JpaRepository interface, which accepts two arguments:

  • The type of entity that it persists, namely a Widget.
  • The type of primary key, which in this case is a Long.

It generates common CRUD method implementations for us to create, update, delete, and find widgets, and then we can define our own query methods using a specific naming convention. For example, we define a findByName() method that returns a List of Widgets. Because “name” is a field in our Widget entity, Spring will generate a query that finds all widgets with the specified name.

Here is our WidgetRepositoryTest class:

package com.infoworld.widgetservice.repository;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.infoworld.widgetservice.model.Widget;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

@DataJpaTest
public class WidgetRepositoryTest {
    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private WidgetRepository widgetRepository;

    private final List<Long> widgetIds = new ArrayList<>();
    private final List<Widget> testWidgets = Arrays.asList(
            new Widget("Widget 1", 1),
            new Widget("Widget 2", 1),
            new Widget("Widget 3", 1)
    );

    @BeforeEach
    void setup() {
        testWidgets.forEach(widget -> {
            entityManager.persist(widget);
            widgetIds.add((Long)entityManager.getId(widget));
        });
        entityManager.flush();
    }

    @AfterEach
    void teardown() {
        widgetIds.forEach(id -> {
            Widget widget = entityManager.find(Widget.class, id);
            if (widget != null) {
                entityManager.remove(widget);
            }
        });
        widgetIds.clear();
    }

    @Test
    void testFindAll() {
        List<Widget> widgetList = widgetRepository.findAll();
        assertEquals(3, widgetList.size());
    }

    @Test
    void testFindById() {
        Widget widget = widgetRepository.findById(
                               widgetIds.getFirst()).orElse(null);

        assertNotNull(widget);
        assertEquals(widgetIds.getFirst(), widget.getId());
        assertEquals("Widget 1", widget.getName());
        assertEquals(1, widget.getVersion());
    }

    @Test
    void testFindByIdNotFound() {
        Widget widget = widgetRepository.findById(
            widgetIds.getFirst() + testWidgets.size()).orElse(null);
        assertNull(widget);
    }

    @Test
    void testCreateWidget() {
        Widget widget = new Widget("New Widget", 1);
        Widget insertedWidget = widgetRepository.save(widget);

        assertNotNull(insertedWidget);
        assertEquals("New Widget", insertedWidget.getName());
        assertEquals(1, insertedWidget.getVersion());
        widgetIds.add(insertedWidget.getId());
    }

    @Test
    void testFindByName() {
        List<Widget> found = widgetRepository.findByName("Widget 2");
        assertEquals(1, found.size(), "Expected to find 1 Widget");

        Widget widget = found.getFirst();
        assertEquals("Widget 2", widget.getName());
        assertEquals(1, widget.getVersion());
    }
}

The WidgetRepositoryTest class is annotated with the @DataJpaTest annotation, which is a slice-testing annotation that loads repositories and entities into the Spring application context and creates a TestEntityManager that we can autowire into our test class. The TestEntityManager allows us to perform database operations outside of our repository so that we can set up and tear down our test scenarios.

In the WidgetRepositoryTest class, we autowire in both our WidgetRepository and TestEntityManager. Then, we define a setup() method that is annotated with JUnit’s @BeforeEach annotation, so it will be executed before each test case runs. Next, we define a teardown() method that is annotated with JUnit’s @AfterEach annotation, so it will be executed after each test completes. The class defines a testWidgets list that contains three test widgets and then the setup() method inserts those into the database using the TestEntityManager’s persist() method. After it inserts each widget, it saves the automatically generated ID so that we can reference it in our tests. Finally, after persisting the widgets, it flushes them to the database by calling the TestEntityManager’s flush() method. The teardown() method iterates over all Widget IDs, finds the Widget using the TestEntityManager’s find() method, and, if it is found, removes it from the database. Finally, it clears the widget ID list so that the setup() method can rebuild it for the next test. (Note that the TestEntityManager removes entities directly; it does not have a remove by ID method, so we first have to find each Widget and then remove them one-by-one.)

Even though most of the methods being tested are autogenerated and well tested, I wanted to demonstrate how to write several kinds of tests. The only method that we really need to test is the findByName() method because that is the only custom method we define. For example, if we were to define the method as findByNam() instead of findByName(), then the method would not work, so it is definitely worth testing.

Conclusion

Spring provides robust support for testing each layer of a Spring MVC application. In this article, we reviewed how to test controllers, using MockMvc; services, using the JUnit Mockito extension; and repositories, using the Spring TestEntityManager. We also reviewed slice testing as a strategy to reduce testing resource utilization and minimize the time required to execute tests. Slice testing is implemented in Spring using the @WebMvcTest and @DataJpaTest annotations. I hope these examples have given you everything you need to feel comfortable writing robust tests for your Spring MVC applications.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Stay Connected

0FansLike
0FollowersFollow
0FollowersFollow
0SubscribersSubscribe
- Advertisement -spot_img

CATEGORIES & TAGS

- Advertisement -spot_img

LATEST COMMENTS

Most Popular

WhatsApp