<div class="page">
<div class="cover text-center">
<img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo">
# Objectes simulats
<div class="text-end fit-content ms-auto my-3 mt-auto pt-3">
<p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p>
<p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p>
<p><strong>Curs:</strong> 2024/2025</p>
</div>
<div>
<p class="fw-bold mb-0">Llicència: BY-NC-SA</p>
<p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank">
<img class="mx-auto" src="/itb/images/license.png" alt="Licence"/>
</a>
</div><!--license-->
</div><!--cover-->
</div><!--page-->
{:toc}
## Introducció
Els __objectes simulats__ o _mock objects_ són objectes que simulen el comportament
d'un objecte real en el nostre sistema.
Això ens permet provar el comportament d'una classe que depén d'altres classes
o components, sense utilitzar la implementació real d'aquests.
D'aquesta manera, podem provar el comportament d'una classe de forma aïllada
i independent de la implementació d'altres classes.
::: info
En una aplicació amb una arquitectura de capes, cada capa depèn
de la implmentació de la capa inferior.
```mermaid
flowchart TD
A[Controller] --> B[Service]
B --> C[Repository]
C --> D[DAO]
```
Per exemple, en la capa de servei (`Service`) depén de la implementació
de la capa de repositori (`Repository`).
Per realitzar les proves unitàries de la capa de servei, caldria __simular__
el comportament de la capa de repositori, mitjançant un objecte simulat (`RepositoryMock`).
```mermaid
flowchart TD
B[Service] --x C[Repository]
B --> E[RepositoryMock]
E:::mock
classDef mock stroke:#f00,stroke-dasharray: 5 5
```
:::
## Arquitectura del codi d'exemple
Aquest exemple simula un projecte organitzat per capes, on
tenim la capa de __servei__ (`Service`) que interactua en la capa de __repositori__ (`Repository`).
```mermaid
flowchart LR
A[CarService] --> B
subgraph service
A --> C[IndicatorService]
end
subgraph repository
B[CarRepository]
end
```
### Entitat `Car`
```java
package ud5.examples.car.domain.entity;
/**
* A simple car class with a constructor and a method to accelerate.
*/
public class Car {
private final String plate;
private final String brand;
private final String model;
private double speed;
public Car(String plate, String brand, String model) {
this.plate = plate;
this.brand = brand;
this.model = model;
this.speed = 0.0;
}
public Car(String plate, String brand, String model, double speed) {
this.plate = plate;
this.brand = brand;
this.model = model;
this.speed = speed;
}
public String getPlate() {
return plate;
}
public String getBrand() {
return brand;
}
public String getModel() {
return model;
}
public double getSpeed() {
return speed;
}
public void accelerate(double speed) {
this.speed += speed;
}
}
```
### Interface `CarService`
```java
package ud5.examples.car.domain.service;
import ud5.examples.car.domain.entity.Car;
import ud5.examples.car.exception.ResourceNotFoundException;
import java.util.List;
public interface CarService {
List<Car> findAll();
Car findByPlate(String plate) throws ResourceNotFoundException;
void accelerate(Car car, double speed);
}
```
### Interface `CarRepository`
```java
package ud5.examples.car.repository;
import ud5.examples.car.domain.entity.Car;
import ud5.examples.car.exception.ResourceNotFoundException;
import java.util.List;
public interface CarRepository {
List<Car> findAll();
Car findByPlate(String plate) throws ResourceNotFoundException;
}
```
### Interface `IndicatorService`
```java
package ud5.examples.car.domain.service;
public interface IndicatorService {
void showMaxSpeedIndicator(boolean showNotification);
}
```
### Exception `ResourceNotFoundException`
```java
package ud5.examples.car.exception;
public class ResourceNotFoundException extends Exception {
public ResourceNotFoundException(String message){
super(message);
}
}
```
### Implementació del servei `CarServiceImpl`
En el següent exemple anem a provar la classe `CarServiceImpl`
que implementa la interfície `CarService`.
Aquesta classe depén de les classes `CarRepository` i `IndicatorService`.
```java
package ud5.examples.car.domain.service.impl;
import ud5.examples.car.domain.entity.Car;
import ud5.examples.car.domain.service.IndicatorService;
import ud5.examples.car.exception.ResourceNotFoundException;
import ud5.examples.car.domain.service.CarService;
import ud5.examples.car.repository.CarRepository;
import java.util.List;
public class CarServiceImpl implements CarService {
private final CarRepository carRepository;
private final IndicatorService indicatorService;
public CarServiceImpl(CarRepository carRepository, IndicatorService indicatorService) {
this.carRepository = carRepository;
this.indicatorService = indicatorService;
}
@Override
public List<Car> findAll() {
return carRepository.findAll();
}
@Override
public Car findByPlate(String plate) throws ResourceNotFoundException {
return carRepository.findByPlate(plate);
}
@Override
public void accelerate(Car car, double speed) {
car.accelerate(speed);
indicatorService.showMaxSpeedIndicator(car.getSpeed() > 120);
}
}
```
## Proves amb objectes simulats
Per realitzar les proves unitàries de la classe `CarServiceImpl`,
cal simular el comportament de les classes `CarRepository` i `IndicatorService`.
A la carpeta `test.mock` cal crear els objectes simulats `CarRepositoryMock` i `IndicatorServiceMock`.
Els objectes simualats han de tindre els mateixos mètodes que les classes reals, per tant,
és necessari que implementen les interfícies corresponents.
```mermaid
flowchart LR
A[CarService] --> E
subgraph service
A --> D[IndicatorServiceMock]
D -.- C[IndicatorService]
end
subgraph repository
E[CarRepositoryMock]
E -.- B[CarRepository]
end
D:::mock
E:::mock
classDef mock stroke:#f00,stroke-dasharray: 5 5
```
### Objecte simulat `CarRepositoryMock`
Aquesta classe simula el comportament de la classe `CarRepository`, la qual
retorna dades de test per realitzar les proves unitàries.
```java
package mock.ud5.examples.car.repository;
import ud5.examples.car.domain.entity.Car;
import ud5.examples.car.exception.ResourceNotFoundException;
import ud5.examples.car.repository.CarRepository;
import java.util.List;
public class CarRepositoryMock implements CarRepository {
private final List<Car> cars = List.of(
new Car("1234ABC", "Seat", "Ibiza"),
new Car("4321ABC", "Renault", "Clio")
);
@Override
public List<Car> findAll() {
return cars;
}
@Override
public Car findByPlate(String plate) throws ResourceNotFoundException {
if (plate.equals("1234ABC")) {
return cars.get(0);
} else if (plate.equals("4321ABC")) {
return cars.get(1);
} else
throw new ResourceNotFoundException("Car with plate " + plate + " not found");
}
}
```
### Objecte simulat `IndicatorServiceMock`
Aquesta classe simula el comportament de la classe `IndicatorService`, la qual
comprova si s'ha mostrat l'indicador de velocitat màxima.
Aquesta classe implementa el mètode `isMaxSpeedIndicatorShown()` que no pertany
a la interfície `IndicatorService` que té com a propòsit comprovar si s'ha mostrat
l'indicador de velocitat màxima. És necessari crear mètodes o estructures addicionals,
ja que el mètode `showMaxSpeedIndicator()` no retorna cap valor i no hi ha
una manera directa de comprovar si s'ha mostrat l'indicador o no.
```java
package mock.ud5.examples.car.domain.service;
import ud5.examples.car.domain.service.IndicatorService;
public class IndicatorServiceMock implements IndicatorService {
private boolean showMaxSpeedIndicator = false;
@Override
public void showMaxSpeedIndicator(boolean showNotification) {
showMaxSpeedIndicator = showNotification;
}
/**
* Mètode del Mock per comprovar l'estat del mètode showMaxSpeedIndicator
* @return true si s'ha cridat el mètode showMaxSpeedIndicator amb true, false en cas contrari
*/
public boolean isMaxSpeedIndicatorShown() {
return showMaxSpeedIndicator;
}
}
```
### Proves unitàries de `CarServiceImplManualTest`
Fent ús dels objectes simulats `CarRepositoryMock` i `IndicatorServiceMock`,
es poden realitzar les proves unitàries de la classe `CarServiceImpl`.
```java
package unit.ud5.examples.car.domain.service.impl;
import mock.ud5.examples.car.domain.service.IndicatorServiceMock;
import mock.ud5.examples.car.repository.CarRepositoryMock;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import ud5.examples.car.domain.entity.Car;
import ud5.examples.car.domain.service.CarService;
import ud5.examples.car.domain.service.impl.CarServiceImpl;
import ud5.examples.car.exception.ResourceNotFoundException;
import static org.junit.jupiter.api.Assertions.*;
class CarServiceImplManualTest {
// Es creen els objectes simulats
private final CarRepositoryMock carRepositoryMock = new CarRepositoryMock();
private final IndicatorServiceMock indicatorServiceMock = new IndicatorServiceMock();
// S'injecten els objectes simulats a la implementació
private final CarService carService = new CarServiceImpl(carRepositoryMock, indicatorServiceMock);
@Nested
class FindAll {
@Test
void shouldReturnAllCars() {
// Internament es crida a carRepositoryMock.findAll()
assertEquals(2, carService.findAll().size());
}
}
@Nested
class FindByPlate {
@Test
void givenExistingCarWithPlate_shouldReturnCar() throws ResourceNotFoundException {
// Internament es crida a carRepositoryMock.findByPlate("1234ABC")
Car car = carService.findByPlate("1234ABC");
assertAll(
() -> assertNotNull(car),
() -> assertEquals("1234ABC", car.getPlate()),
() -> assertEquals("Seat", car.getBrand()),
() -> assertEquals("Ibiza", car.getModel())
);
}
@Test
void givenDifferentExistingCarWithPlate_shouldReturnCar() throws ResourceNotFoundException {
// Internament es crida a carRepositoryMock.findByPlate("4321ABC")
Car car = carService.findByPlate("4321ABC");
assertAll(
() -> assertNotNull(car),
() -> assertEquals("4321ABC", car.getPlate()),
() -> assertEquals("Renault", car.getBrand()),
() -> assertEquals("Clio", car.getModel())
);
}
@Test
void givenNonExistingCarWithPlate_shouldThrowResourceNotFoundException() {
// Internament es crida a carRepositoryMock.findByPlate("0000ZZZ")
assertThrows(ResourceNotFoundException.class, () -> carService.findByPlate("0000ZZZ"));
}
}
@Nested
class Accelerate {
@Test
void shouldAccelerateCar() {
Car car = new Car("1234ABC", "Seat", "Ibiza");
carService.accelerate(car, 10.0);
assertEquals(10.0, car.getSpeed());
}
@Test
void givenAcceleratedCarLessThanMaximumSpeed_shouldNotShowMaxSpeedIndicator() {
Car car = new Car("1234ABC", "Seat", "Ibiza");
// Internament es crida a indicatorServiceMock.showMaxSpeedIndicator(false)
carService.accelerate(car, 10.0);
assertFalse(indicatorServiceMock.isMaxSpeedIndicatorShown());
}
@Test
void givenAcceleratedCarGreaterThanMaximumSpeed_shouldShowMaxSpeedIndicator() {
Car car = new Car("1234ABC", "Seat", "Ibiza");
// Internament es crida a indicatorServiceMock.showMaxSpeedIndicator(true)
carService.accelerate(car, 130.0);
assertTrue(indicatorServiceMock.isMaxSpeedIndicatorShown());
}
}
}
```