# Lab 04 : Testing multi-layer applications

# Useful commands 

```mvn test -Dtest=EmployeeService*```


<br>

# 1. Architecture layers and test scopes 

| **Scope**                             | **Strategy**                                          | **Annotation(s)**                                                       | **What It Validates**                                         | **Example Task**                                                                                    |
| ------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **A. Repository Test**                | Test only the persistence layer using an in-memory DB | `@DataJpaTest`                                                                      | Validates **data persistence, queries, and JPA mappings**     | Create an `Employee`, save it, and verify it can be found by a custom query                         |
| **B. Service Unit Test**              | Mock the repository to isolate business logic         | JUnit + Mockito (`@Mock`, `@InjectMocks`)                                           | Validates **business rules** without hitting the database     | Mock `EmployeeRepository.findAll()` and verify the service returns the expected list                |
| **C. Controller (Web Layer) Test**    | Mock the service and test REST endpoints              | `@WebMvcTest` + `@MockBean`                                                         | Validates **request/response handling and API behavior**      | Perform a GET request to `/api/employees` and verify JSON structure and status 200                  |
| **D. Integration Test (Server-side)** | Load full Spring context and use `MockMvc`            | `@SpringBootTest` + `@AutoConfigureMockMvc`                                         | Validates **end-to-end logic within the application context** | Simulate calling `/api/employees` and confirm data flows through layers  |
| **E. Integration Test (Client-side)** | Run app on a real port and test via HTTP client       | `@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)` + `TestRestTemplate` | Validates **real API behavior** with live HTTP requests       | Use `TestRestTemplate` to send a GET request and assert the JSON list response from the running app |


## 1.1. Repository test - **EmployeeRepositoryTest.java**

- Loads the JPA layer.  
- Uses H2 database.
- Verifies persistence and query logic.

```java
@DataJpaTest
class EmployeeRepositoryTest {

    @Autowired
    private EmployeeRepository repository;

    @Test
    void whenFindByName_thenReturnEmployee() {
        Employee emp = new Employee(null, "Ana", "Engineering");
        repository.save(emp);

        List<Employee> result = repository.findByName("Ana");

        assertThat(result).isNotEmpty();
        assertThat(result.get(0).getDepartment()).isEqualTo("Engineering");
    }
}
```
<br>

## 1.2. Service unit test - **EmployeeServiceUnitTest.java**

- Uses Mockito.  
- Mocks the repository.  
- Validates business logic only.

```java
@ExtendWith(MockitoExtension.class)
class EmployeeServiceUnitTest {

    @Mock
    private EmployeeRepository repository;

    @InjectMocks
    private EmployeeServiceImpl employeeService;

    @Test
     void whenSearchValidName_thenEmployeeShouldBeFound() {
        String name = "alex";
        Optional<Employee> found = employeeService.getEmployeeByName(name);

        assertThat(found.get().getName()).isEqualTo(name);
    }

}
```

<br>

## 1.3. Controller test - **EmployeeControllerWithMockServiceTest.java**

- Loads only the web layer (controller).  
- Mocks the service with @MockBean  
- Validates API routing and JSON responses.  

```java
@WebMvcTest(EmployeeRestController.class)
class EmployeeControllerWithMockServiceTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    @Test
    void givenManyEmployees_whenGetEmployees_thenReturnJsonArray() throws Exception  {
        
        when(service.getAllEmployees()).thenReturn(List.of(new Employee(1L, "Bruno", "HR")));

        mvc.perform(get("/api/employees"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].name").value("Bruno"));

        verify(service, times(1)).getAllEmployees();
    }
}
```

<br>

## 1.4. Integration Test (Server-side) - **EmployeeRestControllerIT.java**

- Loads the entire Spring context.
- Uses the real service and repository.
- Simulates HTTP calls with MockMvc.
- Confirms end-to-end logic inside the app.

```java
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
class EmployeeRestControllerIT {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    @AfterEach
     void resetDb() {
        repository.deleteAll();
    }

    @Test
     void whenValidInput_thenCreateEmployee() throws Exception {
        Employee bob = new Employee("bob", "bob@deti.com");
        mvc.perform(post("/api/employees").contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(bob)));

        List<Employee> found = repository.findAll();
        assertThat(found).extracting(Employee::getName).containsOnly("bob");
    }

    @Test
     void givenEmployees_whenGetEmployees_thenStatus200() throws Exception {
        createTestEmployee("bob", "bob@deti.com");
        createTestEmployee("alex", "alex@deti.com");

        mvc.perform(get("/api/employees").contentType(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(2))))
                .andExpect(jsonPath("$[0].name", is("bob")))
                .andExpect(jsonPath("$[1].name", is("alex")));
    }

    private void createTestEmployee(String name, String email) {
        Employee emp = new Employee(name, email);
        repository.saveAndFlush(emp);
    }
}
```

<br>

## 1.5. Integration Test (Client-side) - **EmployeeRestControllerTemplateIT.java**

- Runs the real application on a random port.  
- Calls endpoints using an HTTP client.  
- Verifies external API behavior.

```java

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class ScopeEEmployeeRestControllerTemplateIT {

    @LocalServerPort
    int randomServerPort;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private EmployeeRepository repository;

    @AfterEach
    void resetDb() {
        repository.deleteAll();
    }

    @Test
     void whenCreateEmployeeWithSuccess() {
        Employee bob = new Employee("bob", "bob@deti.com");
        restTemplate.postForEntity("/api/employees", bob, Employee.class);

        List<Employee> found = repository.findAll();
        assertThat(found).extracting(Employee::getName).contains("bob");
    }

    @Test
     void givenEmployees_whenGetEmployees_thenStatus200()  {
        createTestEmployee("bob", "bob@deti.com");
        createTestEmployee("alex", "alex@deti.com");


        ResponseEntity<List<Employee>> response = restTemplate
                .exchange("/api/employees", HttpMethod.GET, null, new ParameterizedTypeReference<>() {
                });

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).extracting(Employee::getName).containsExactly("alice", "bob", "alex");
    }

    private void createTestEmployee(String name, String email) {
        Employee emp = new Employee(name, email);
        repository.saveAndFlush(emp);
    }
}
```
<br>

## 2. Example application properties

**application.properties**
```
spring.datasource.url=jdbc:postgresql://localhost:5432/employee_db
spring.datasource.username=admin
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
```
<br>

**application-integrationtest.properties**
```
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=a
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
logging.level.root=ERROR
```

<br>

# 3. REST Assured Library

The REST Assured library can be used to test REST APIs from the perspective of an external client (interacting with the application like a real HTTP client)

<br>

## 3.1. Pom.xml dependencies 


```
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>
```
 
REST-assured takes advantage of the power of **Hamcrest** matchers to perform its assertions, so we must include the hamcrest dependency as well:

```
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.1</version>
</dependency>
```

## 3.2. How to use 

```java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MealsBookingRestAssuredIT {

    @LocalServerPort
    private int port;

    @BeforeAll
    static void setup() {
        RestAssured.baseURI = "http://localhost";
    }

    @Test
    void whenPostBooking_thenReturnCreated() {
        Bookings booking = new Bookings("userA", LocalDate.now().plusDays(1), Bookings.bShift.LUNCH);

        given()
            .contentType(ContentType.JSON)
            .body(booking)
        .when()
            .post("http://localhost:" + port + "/bookings")
        .then()
            .statusCode(201)
            .body("userId", equalTo("userA"))
            .body("shift", equalTo("LUNCH"));
    }

    @Test
    void whenGetAllBookings_thenReturnList() {
        when()
            .get("http://localhost:" + port + "/bookings")
        .then()
            .statusCode(200)
            .contentType(ContentType.JSON)
            .body("size()", greaterThanOrEqualTo(0));
    }

    @Test
    void whenCancelBooking_thenReturnOk() {
        when()
            .put("http://localhost:" + port + "/bookings/1/cancel")
        .then()
            .statusCode(anyOf(is(200), is(404)));
    }
}
```

<br>

# 4. Class Notes

## 4.1. Difference between standard @Mock and @MockBean
- @Mock in simple JUnit + Mockito tests (in plain unit tests) - Spring does not inject it into any dependent beans, have to manually wire it into classes.
  
- @MockBean when part of the Spring context needs to be loaded (in a @WebMvcTest for controllers) - Spring injects it automatically into any dependent beans.

<br>

## 4.2. The role of the file “application-integrationtest.properties”
**application-integrationtest.properties** is a special configuration file used when running integration tests, allowing to override the default application configuration (**application.properties**) only during integration testing.

When running integration tests, you should:

- use a different database (H2 instead of production DB).
- use a different server port.
- disable security, logging, or caching for testing.  

This file lets you define such settings without touching the main configuration used in development or production.

It is used when:
```java
@ActiveProfiles("integrationtest")
@SpringBootTest
class EmployeeRestControllerIT {
    // (...)
}
```

## 4.3. Top Down Testing

In top down testing, we start testing from the higher level components (controllers, services) and use mocks for the lower level ones (repositories, external systems), making us start with testing user functionality early (endpoints, service workflows), and test the code with mocks until we arrive at the service components.

How this can help with TDD:
- Start from business goals (high-level tests)
- Define interfaces before implementations
- Progressive testing

This way, we are defining behavior before implementation and basing the design from tests which are the goals of TDD.


## 4.4. Car Rental Core Tests
- **Repository tests:**  database queries that correctly filter cars by segment, motor type, and availability (using @DataJpaTest)

- **Service tests:**  which cars are considered “similar.” Using a mocked repository (with Mockito) to simulate data and verify that the service layer correctly selects a suitable car or throws an exception when no replacement is available.

- **Controller Tests:**  testing the endpoints using @SpringBootTest with TestRestTemplate and using @WebMvcTest to verify HTTP responses, status codes, and JSON content.

- **Integration Tests:**  test all layers (controller, service, and repository) to verify that the full workflow works correctly when the application runs completely.