TestingPro· 60 min read

Testing: JUnit, Mockito & Spring Test Slices

Automated tests prove your code works and keep it working as you change it — every Spring job expects you to write them. Here are the three kinds you need.

What you will learn

  • Write a plain unit test with JUnit 5
  • Test a service in isolation by mocking its repository with Mockito
  • Test a controller with @WebMvcTest and MockMvc

What is an automated test, and why bother?

An automated test is code that checks your other code does the right thing — it runs the code with some input and asserts (insists) the result is what you expect. Run all your tests with one command and you instantly know whether anything broke.

Why employers insist on this: as an app grows, manual clicking cannot cover everything, and a small change can quietly break something far away. Tests catch those breaks the moment they happen. In Spring you write three kinds, from narrow to broad:

KindTestsTool
Unit testOne class/method in isolationJUnit 5 (+ Mockito)
Slice testOne layer wired by Spring@WebMvcTest / @DataJpaTest
Integration testThe whole app together@SpringBootTest

A plain unit test with JUnit 5

JUnit 5 is the standard Java testing library (it comes with every Spring Boot project). A test is a method marked @Test; inside it you call your code and use an assertion to state what the answer must be. Say we have a tiny service:

The code under test
@Service
public class PriceService {
    public double withTax(double price) {
        return price * 1.18;   // add 18% tax
    }
}
A JUnit 5 unit test with one assertion
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class PriceServiceTest {

    @Test
    void addsEighteenPercentTax() {
        PriceService service = new PriceService();
        double result = service.withTax(1000);
        // assert: the result MUST equal 1180
        assertEquals(1180.0, result);
    }
}

Note: Output (running the tests): PriceServiceTest > addsEighteenPercentTax() PASSED Tests run: 1, Failures: 0 assertEquals(1180.0, result) checked 1000 × 1.18 = 1180.0. It passed, so the bar is green. If someone later broke the tax rate, this test would FAIL and tell you immediately.

Testing a service that needs a repository: Mockito

Most services depend on a repository — but in a unit test we do not want to touch a real database (slow, and a database failure should not fail a logic test). The solution is a mock: a fake stand-in object that returns exactly what we tell it to. Mockito is the standard mocking library.

Say a ProductService fetches a product and throws if it is missing. We mock the repository so the test is fast and focused purely on the service’s logic:

Mocking the repository so the service is tested alone
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Optional;

class ProductServiceTest {

    @Test
    void returnsProductWhenItExists() {
        // 1) create a FAKE repository
        ProductRepository repo = Mockito.mock(ProductRepository.class);
        // 2) tell the fake what to return for findById(1)
        when(repo.findById(1L))
            .thenReturn(Optional.of(new Product("Mouse", 499.0)));

        ProductService service = new ProductService(repo);

        // 3) call the real service (it uses the fake repo)
        Product p = service.getProduct(1L);

        // 4) assert the logic did the right thing
        assertEquals("Mouse", p.getName());
    }
}

Note: Output: ProductServiceTest > returnsProductWhenItExists() PASSED No database was touched. Mockito.mock(...) built a fake ProductRepository; when(...).thenReturn(...) scripted its answer; the service ran its real logic against that fake. This is a true unit test — fast and isolated.

Testing a controller with @WebMvcTest and MockMvc

To test a controller — the web layer — Spring gives you a slice test. @WebMvcTest starts only the web layer (not the database), and MockMvc lets you send fake HTTP requests and check the response, without a running server.

A controller slice test with MockMvc
@WebMvcTest(ProductController.class)
class ProductControllerTest {

    @Autowired MockMvc mockMvc;          // sends fake HTTP requests
    @MockBean ProductService service;     // a mock of the service it depends on

    @Test
    void getReturnsProductJson() throws Exception {
        when(service.getProduct(1L))
            .thenReturn(new Product("Mouse", 499.0));

        mockMvc.perform(get("/products/1"))
            .andExpect(status().isOk())                       // 200
            .andExpect(jsonPath("$.name").value("Mouse"));    // body check
    }
}

Note: Output: ProductControllerTest > getReturnsProductJson() PASSED MockMvc sent a fake GET /products/1 (no real server, no real database — the service is a mock). It then checked two things: the status was 200 OK, and the JSON field name equalled "Mouse". This verifies the web layer maps the URL and serialises JSON correctly.

The other slices, in one line each

  • @DataJpaTest — tests just your repository/JPA layer against a fast in-memory database, so you can verify a custom query really works.
  • @SpringBootTest — starts the whole application context for a full integration test, when you want to check that all the layers work together end to end.

Tip: Aim for many fast unit tests (with Mockito), some slice tests (@WebMvcTest/@DataJpaTest) for each layer, and a few broad @SpringBootTest integration tests. This “test pyramid” — lots of small, few large — keeps your suite both thorough and fast.

Watch out: Use a real database only where it matters (@DataJpaTest uses a fast in-memory one). For unit-testing logic, mock the dependencies — a unit test that hits a real database is slow and can fail for reasons unrelated to the code you are testing.

Q. Why mock the repository when unit-testing a service with Mockito?

Answer: A mock is a scripted fake. It lets you test only the service’s logic, quickly and reliably, without a real database — so a database problem can never make a pure logic test fail.

✍️ Practice

  1. Write a JUnit 5 test for a small method (e.g. a withTax or add method) with one assertEquals.
  2. Use Mockito to mock a repository and unit-test a service method that calls findById.

🏠 Homework

  1. Add tests to your Task API: a JUnit unit test for a service method (repository mocked with Mockito), and a @WebMvcTest + MockMvc test that GET /tasks/{id} returns 200 and the right JSON.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →