<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()); } } } ```