Sobes.tech
Middle

What tests do you write and how?

sobes.tech AI

Answer from AI

I write the following types of tests:

  • Unit tests: Test individual components or class methods in isolation from the rest of the system. I use JUnit or TestNG. For mocking dependencies — Mockito.
  • Integration tests: Test interactions between multiple components, subsystems, or services. For example, interaction with a database or external API. I use Spring Boot Test for testing Spring applications, Testcontainers for testing with real dependencies (databases, message brokers).
  • Component tests / End-to-End (E2E) tests: Test the system from the user's perspective from start to finish. Mostly automated UI or API tests. For API, I use Rest-Assured. For UI — Selenium (although I prefer writing API tests as they are more stable and faster).

Test writing process:

  1. Choosing a framework: JUnit 5 or TestNG for unit tests, Spring Boot Test, Rest-Assured, Testcontainers for integration and component tests.
  2. Writing tests before or during code development: I use TDD (Test-Driven Development) or write tests immediately after implementing functionality.
  3. Test isolation: For unit tests, I use Mockito to mock dependencies, testing only the isolated component.
  4. Test structure: I usually follow the Arrange-Act-Assert structure.
  5. Clear test naming: Test names should clearly describe what is being tested and under what conditions. For example, userService_createUser_shouldSaveUserInDatabase.
  6. Testing boundary conditions and errors: I check both normal scenarios and cases with incorrect input data, exceptions, and boundary values.
  7. Refactoring tests: I keep tests clean and readable, just like the main code.

Example of a unit test with Mockito and JUnit 5:

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;

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

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock // Dependency we mock
    private UserRepository userRepository;

    @InjectMocks // Service we test
    private UserService userService;

    @Test
    void createUser_shouldSaveUserInDatabase() {
        // Arrange: Prepare data and mock behavior
        User user = new User("Alice", "alice@example.com");
        when(userRepository.save(any(User.class))).thenReturn(user); // Mock save method

        // Act: Perform the action
        User createdUser = userService.createUser(user);

        // Assert: Verify the result
        assertEquals("Alice", createdUser.getName()); // Check user creation with correct name
        verify(userRepository, times(1)).save(any(User.class)); // Verify save was called once
    }
}

Example of using Rest-Assured for an API integration test:

import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

class UserControllerIntegrationTest {

    @BeforeAll
    static void setup() {
        // Setup base URL for tests
        RestAssured.baseURI = "http://localhost:8080";
        RestAssured.port = 8080;
    }

    @Test
    void getUserById_shouldReturnUser() {
        // Perform request and verify response
        given()
            .when()
            .get("/users/1") // URL to get user by ID
            .then()
            .statusCode(200) // Check response status
            .body("name", equalTo("John Doe")); // Check response body field
    }
}