Unit Tests with JUnit 5 and Mockito

Testing the Service

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

    @Mock ProductRepository repo;
    @Spy ProductMapper mapper = new ProductMapperImpl();     // real MapStruct mapper
    @InjectMocks ProductService service;

    @Test
    void createSavesNewProduct() {
        var req = new CreateProductRequest("ELE-1001", "Wireless Mouse", "2.4 GHz",
                new BigDecimal("799.00"), 120, "Electronics");
        when(repo.existsBySku("ELE-1001")).thenReturn(false);
        when(repo.save(any(Product.class))).thenAnswer(inv -> {
            Product p = inv.getArgument(0);
            p.setId(1L);
            return p;
        });

        ProductResponse res = service.create(req);

        assertThat(res.id()).isEqualTo(1L);
        assertThat(res.inStock()).isTrue();
        verify(repo).save(any(Product.class));
    }

    @Test
    void createRejectsDuplicateSku() {
        when(repo.existsBySku("ELE-1001")).thenReturn(true);
        var req = new CreateProductRequest("ELE-1001", "Mouse", null, BigDecimal.TEN, 1, "Electronics");

        assertThatThrownBy(() -> service.create(req))
                .isInstanceOf(DuplicateSkuException.class)
                .hasMessage("SKU ELE-1001 already exists");
        verify(repo, never()).save(any());
    }
}

Example

./mvnw test -Dtest=ProductServiceTest

Example output

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.shopease.product.ProductServiceTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.412 s
[INFO] BUILD SUCCESS

Mockito Cheat Sheet

CodeMeaning
when(repo.findById(1L)).thenReturn(Optional.of(p))Stub a return value
when(repo.save(any())).thenThrow(new RuntimeException())Stub an exception
verify(repo).save(p)Was it called once with p?
verify(repo, never()).deleteById(any())Was it never called?
ArgumentCaptor<Product> + verify(repo).save(captor.capture())Inspect what was passed

Tip: Name tests after behaviour (createRejectsDuplicateSku), and follow Arrange / Act / Assert inside each test.