Spring Boot HandBook

    Introduction :#

    Mockito is a Java testing framework used to create mock objects, which allow developers to isolate and test specific units of code without relying on external dependencies like databases or web services. It is often used with JUnit to test interactions between objects by simulating behaviors and verifying method calls.

    For example, Mockito lets you:

    • Mock objects to simulate their behavior.
    • Stub methods to return specific values.
    • Verify interactions to ensure methods are called with expected arguments.

    This helps ensure that your code works as expected in isolation, leading to more reliable and faster unit tests.

    Mockito#

    Mockito is useful for testing components in isolation without relying on their actual implementations or external dependencies. Mockito can be used for: • Mocking: Creating mock objects to simulate the behavior of real objects. • Stubbing: Defining the behavior of mock objects when specific methods are called. • Verification: Checking if certain methods were called on mock objects

    Real Life Analogy#

    Imagine you are testing a car's navigation system, but you don't want to actually drive the car or use real GPS satellites every time you run a test. Instead, you mock the GPS signal to simulate different locations and routes. This allows you to test how the navigation system reacts without needing the actual GPS system or a physical car.

    In the same way, Mockito lets you mock dependencies in your code. For example, if you have a UserService class that depends on a UserRepository to fetch users from a database, you can mock the UserRepository so that, instead of making a real database call, it returns pre-defined data. This allows you to test the behavior of UserService without involving the actual database.

    Real-Life Analogy with Mockito Terms:#

    • Mocking: Pretending to have a real GPS signal (or other service) without actually using the real one.
    • Stubbing: Telling the GPS to always say you are at a specific location.
    • Verifying: Checking if the navigation system asked the GPS for your current location.

    This approach makes testing easier, faster, and more focused on the component you're testing (in this case, the navigation system). Similarly, Mockito makes unit testing in code more isolated and efficient.

    Mockito Methods#

    Mockito methods and techniques help create flexible and robust unit tests by simulating real interactions without relying on actual implementations.

    • Creating Mock:
      • With @Mock Annotation: Automatically injects a mock object.
      • With Mockito.mock(Classname): Manually creates a mock object.
    • Stubbing Mock:
      • when(T methodCall): Specifies a method call to be stubbed.
      • thenReturn(T value): Sets a return value for a stubbed method call.
      • thenThrow(Throwable... throwables): Throws an exception when a stubbed method call is made.
    • Verifying Methods:
      • verify(T mock): Verifies that a method was called on a mock
      • verify(T mock, VerificationMode mode): Verifies a method was called with a specific verification mode.
      • Verification Modes:
        • times(int wantedNumberOfInvocations): Verifies the exact number of method invocations.
        • never(): Verifies that a method was never called.
        • atLeastOnce(): Verifies that a method was called at least once.
        • atLeast(int minNumberOfInvocations): Verifies that a method was called at least a specified number of times.
        • atMost(int maxNumberOfInvocations): Verifies that a method was called no more than a specified number of times.
        • only(): Verifies that no other method was called on the mock.
    • Argument Captor:
      • Captures arguments passed to a method call for further assertions or validation.

    Implementation and Explanation#

    • Code
      • Go to the Service class.
    import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import java.util.List; @Service @RequiredArgsConstructor @Slf4j public class EmployeeService { private final EmployeeRepository employeeRepository; private final ModelMapper modelMapper; public EmployeeDto getEmployeeById(Long id) { log.info("Fetching employee with id: {}", id); EmployeeEntity employee = employeeRepository.findById(id) .orElseThrow(() -> { log.error("Employee not found with id: {}", id); return new ResourceNotFoundException("Employee not found with id: " + id); }); log.info("Successfully fetched employee with id: {}", id); return modelMapper.map(employee, EmployeeDto.class); } public EmployeeDto createNewEmployee(EmployeeDto employeeDto) { log.info("Creating new employee with email: {}", employeeDto.getEmail()); List<EmployeeEntity> existingEmployees = employeeRepository.findByEmail(employeeDto.getEmail()); if (!existingEmployees.isEmpty()) { log.error("Employee already exists with email: {}", employeeDto.getEmail()); throw new RuntimeException("Employee already exists with email: " + employeeDto.getEmail()); } EmployeeEntity newEmployee = modelMapper.map(employeeDto, EmployeeEntity.class); EmployeeEntity savedEmployee = employeeRepository.save(newEmployee); log.info("Successfully created new employee with id: {}", savedEmployee.getId()); return modelMapper.map(savedEmployee, EmployeeDto.class); } }
    • Press CTRL + SHIFT + T and create a Test file and implements and tests all method.
    import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.modelmapper.ModelMapper; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.context.annotation.Import; import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; //@DataJpaTest //this only scan the repository //@SpringBootTest //this scan whole application @ExtendWith(MockitoExtension.class) @Import(TestContainerConfiguration.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) //to use docker container only class EmployeeServiceTest { @Mock private EmployeeRepository employeeRepository; //to create mock @Spy private ModelMapper modelMapper; @InjectMocks private EmployeeService employeeService; private EmployeeEntity mockEmployeeEntity; private EmployeeDto mockEmployeeDto; private Long id = 1L; //Create an employee @BeforeEach void setup(){ mockEmployeeEntity = EmployeeEntity .builder() .id(id) .name("Alic") .email("alice@gmail.com") .salary(100000.00) .build(); mockEmployeeDto = modelMapper.map(mockEmployeeEntity,EmployeeDto.class); } // Test getEmployeeById() when EmployeeId is present then return EmployeeDto @Test public void testGetEmployeeById_WhenEmployeeIdIsPresent_ThenReturnEmployeeDto(){ //assign when(employeeRepository.findById(id)).thenReturn(Optional.of(mockEmployeeEntity)); // to stub mock //act EmployeeDto employeeDto = employeeService.getEmployeeById(1L); //assert assertThat(employeeDto).isNotNull(); assertThat(employeeDto.getId()).isEqualTo(mockEmployeeEntity.getId()); assertThat(employeeDto.getEmail()).isEqualTo(mockEmployeeEntity.getEmail()); verify(employeeRepository,only()).findById(id); //to verify mock } // Test createNewEmployee() when employee valid then create new employee @Test public void testCreateNewEmployee_WhenValidEmployee_ThenCreateNewEmployee(){ //assign when(employeeRepository.findByEmail(anyString())).thenReturn(List.of()); when(employeeRepository.save(any(EmployeeEntity.class))).thenReturn(mockEmployeeEntity); //act EmployeeDto employeeDto = employeeService.createNewEmployee(mockEmployeeDto); //to stub mock //assert assertThat(employeeDto).isNotNull(); assertThat(employeeDto.getEmail()).isEqualTo(mockEmployeeDto.getEmail()); ArgumentCaptor<EmployeeEntity> employeeEntityArgumentCaptor = ArgumentCaptor.forClass(EmployeeEntity.class); //argument captor verify(employeeRepository).save(employeeEntityArgumentCaptor.capture()); //to verify mock EmployeeEntity captorEmployeeEntity = employeeEntityArgumentCaptor.getValue(); assertThat(captorEmployeeEntity.getEmail()).isEqualTo(mockEmployeeEntity.getEmail()); } }
    • Explanation

    This unit test is for the EmployeeService class, specifically testing the getEmployeeById and createNewEmployee methods using Mockito for mocking dependencies. Here's a detailed breakdown of the code:

    • Test Setup
      • Annotations:
        • @ExtendWith(MockitoExtension.class): This annotation integrates Mockito with JUnit 5. It initializes mocks, spies, and injects them into the test class.
        • @Import(TestContainerConfiguration.class): This imports the configuration needed to set up Testcontainers for database integration.
        • @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE): This tells Spring Boot not to replace the database configuration with an in-memory database. Instead, it uses the database provided by Testcontainers.
      • Fields:
        • @Mock: Creates a mock instance of EmployeeRepository. Mocks are used to simulate the behavior of real objects and control their responses.
        • @Spy: Creates a spy instance of ModelMapper. Spies allow partial mocking, meaning you can call real methods while still controlling certain aspects.
        • @InjectMocks: Creates an instance of EmployeeService and injects the mocks and spies into it.
    • Setup Method
    @BeforeEach void setup(){ mockEmployeeEntity = EmployeeEntity .builder() .id(id) .name("Alic") .email("alice@gmail.com") .salary(100000.00) .build(); mockEmployeeDto = modelMapper.map(mockEmployeeEntity, EmployeeDto.class); }
    • @BeforeEach: This method runs before each test. It initializes mockEmployeeEntity and mockEmployeeDto with some default values that will be used in the tests.
    • Test Methods
      1. Testing getEmployeeById():
    // Test getEmployeeById() when EmployeeId is present then return EmployeeDto @Test public void testGetEmployeeById_WhenEmployeeIdIsPresent_ThenReturnEmployeeDto(){ //assign when(employeeRepository.findById(id)).thenReturn(Optional.of(mockEmployeeEntity)); // to stub mock //act EmployeeDto employeeDto = employeeService.getEmployeeById(1L); //assert assertThat(employeeDto).isNotNull(); assertThat(employeeDto.getId()).isEqualTo(mockEmployeeEntity.getId()); assertThat(employeeDto.getEmail()).isEqualTo(mockEmployeeEntity.getEmail()); verify(employeeRepository,only()).findById(id); //to verify mock }
    • Explanation
      • Assign: Sets up the mock repository to return mockEmployeeEntity when findById(id) is called.
      • Act: Calls the getEmployeeById method of employeeService.
      • Assert: Verifies that the returned EmployeeDto is not null and has the expected values.
      • Verify: Checks that findById(id) was called exactly once.
    1. Testing createNewEmployee():
      • Code
    // Test createNewEmployee() when employee valid then create new employee @Test public void testCreateNewEmployee_WhenValidEmployee_ThenCreateNewEmployee(){ //assign when(employeeRepository.findByEmail(anyString())).thenReturn(List.of()); when(employeeRepository.save(any(EmployeeEntity.class))).thenReturn(mockEmployeeEntity); //act EmployeeDto employeeDto = employeeService.createNewEmployee(mockEmployeeDto); //to stub mock //assert assertThat(employeeDto).isNotNull(); assertThat(employeeDto.getEmail()).isEqualTo(mockEmployeeDto.getEmail()); ArgumentCaptor<EmployeeEntity> employeeEntityArgumentCaptor = ArgumentCaptor.forClass(EmployeeEntity.class); //argument captor verify(employeeRepository).save(employeeEntityArgumentCaptor.capture()); //to verify mock EmployeeEntity captorEmployeeEntity = employeeEntityArgumentCaptor.getValue(); assertThat(captorEmployeeEntity.getEmail()).isEqualTo(mockEmployeeEntity.getEmail()); }
    • Explanation
      • Assign:
        • Sets up the mock repository to return an empty list when findByEmail is called, simulating no existing employee with the given email.
        • Configures save to return mockEmployeeEntity when called.
      • Act: Calls createNewEmployee with mockEmployeeDto to test employee creation.
      • Assert:
        • Checks that the returned EmployeeDto is not null and its email matches mockEmployeeDto's email.
        • Uses ArgumentCaptor to capture the argument passed to save and verifies that the captured EmployeeEntity has the expected email.
      • Verify: Ensures that the save method was called once and captures the argument used in that call.
    • Output

    Here, we are testing whether the Service class is functioning correctly.

    Here, we have already covered these aspects, which are highlighted in green in the image.

    Last updated on Dec 09, 2024