06_2 - Capa dominio: Test

Para comprobar que todo funciona correctamente, tenemos que usar tests automáticos obligatoriamente, ya que no podemos arrancar la aplicación (en realidad, da igual que tengamos un método main(), siempre deberíamos usar test para comprobar nuestros componentes).

Nuestros modelos, a la larga, tendrán lógica de negocio que necesitamos probar. Por ejemplo, en nuestro modelo Book tenemos un test que calcula el precio final en función del precio base y el descuento:

    public BigDecimal calculateFinalPrice() {
        BigDecimal discount = basePrice
                .multiply(BigDecimal.valueOf(discountPercentage))
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);

        return basePrice.subtract(discount).setScale(2, RoundingMode.HALF_UP);
    }

Los test tienen que probar situaciones, no métodos. Es decir, tenemos que pensar diferentes situaciones que se puedan dar a la hora de calcular el precio final. Para ello, usaremos tests parametrizados para probar diferentes escenarios:

    @ParameterizedTest(name = "{index} => basePrice={0}, discountPercentage={1}, expectedPrice={2}")
    @DisplayName("Calculate final price with various discounts")
    @CsvSource({
            "100.00, 15.0, 85.00",
            "50.00, 0.0, 50.00",
            "75.00, 100.0, 0.00",
            "60.00, -10.0, 60.00"
    })
    void calculateFinalPrice(String basePrice, double discountPercentage, String expectedPrice) {
        Book book = new Book(
                "978-3-16-148410-0",
                "Título en Español",
                "Title in English",
                "Sinopsis en Español",
                "Synopsis in English",
                new BigDecimal(basePrice),
                discountPercentage,
                "cover.jpg",
                LocalDate.of(2023, 1, 1),
                null,
                null
        );
        BigDecimal expected = new BigDecimal(expectedPrice).setScale(2, java.math.RoundingMode.HALF_UP);
        assertEquals(expected, book.getPrice());
    }

En este caso, vemos que el último test falla, cuando el descuento es negativo. Se supone que si el descuento es negativo, asumimos que es un error y que debería ser cero. De ahí la importancia de probar situaciones diferentes y TDD. Primero pensamos posibles escenarios y después vamos refactorizando el código para que todo funcione correctamente.

Otra funcionalidad que debemos probar es la de añadir autores a un libro:

    public void addAuthor(Author author) {
        this.authors.add(author);
    }

¿Qué pasa si añadimos un autor que ya existe? ¿Y si añadimos un autor a un libro que no tiene ningún autor todavía? Como ves, tenemos que pensar en diferentes situaciones y crear tests adecuados a cada una de ellas. De esta forma, iremos depurando el código con la funcionalidad que necesitemos.

Los tests anteriores son sólo algunos ejemplos básicos. Siempre que añadamos lógica de negocio a nuestros modelos, deberíamos empezar por crear los tests probando diferentes posibilidades, ya que, a la larga, nos ayudarán a ser más rápidos y eficientes

En cuanto a los mapeadores, tenemos que probar situaciones como que nos pasen un objeto null para mapear, ¿debería devolver null o dar una excepción? Dependiendo de cómo queramos que funcione nuestra aplicación, refactorizaremos nuestro código en función de los tests.

Además, tenemos que probar los mapeos entre diferentes clases, con listados de objetos… Algunos ejemplos de tests pueden ser:

    @Test
    @DisplayName("Test map Book to BookDto")
    void toBookDto() {
        // Arrange
        var book = new Book(
                "978-84-376-0494-7",
                "TitleEs",
                "TitleEn",
                "SynopsisEs",
                "SynopsisEn",
                new BigDecimal("20.00"),
                10,
                "cover.jpg",
                LocalDate.of(2020, 1, 1),
                null,
                null
        );
        book.setPublisher(new Publisher("Publisher Name", "publisher-slug"));
        book.setAuthors(List.of(
                new Author("Author One", "nationality1", "Bio1", "Bio1En", 1970, null, "author-one"),
                new Author("Author Two", "nationality2", "Bio2", "Bio2En", 1980, 2020, "author-two")
        ));

        // Act
        var bookDto = BookMapper.getInstance().fromBookToBookDto(book);

        // Assert
        assertAll(
                () -> assertEquals(book.getIsbn(), bookDto.isbn(), "ISBN should match"),
                () -> assertEquals(book.getTitleEs(), bookDto.titleEs(), "TitleEs should match"),
                () -> assertEquals(book.getTitleEn(), bookDto.titleEn(), "TitleEn should match"),
                () -> assertEquals(book.getSynopsisEs(), bookDto.synopsisEs(), "SynopsisEs should match"),
                () -> assertEquals(book.getSynopsisEn(), bookDto.synopsisEn(), "SynopsisEn should match"),
                () -> assertEquals(book.getBasePrice(), bookDto.basePrice(), "BasePrice should match"),
                () -> assertEquals(book.getDiscountPercentage(), bookDto.discountPercentage(), "DiscountPercentage should match"),
                () -> assertEquals(book.getCover(), bookDto.cover(), "Cover should match"),
                () -> assertEquals(book.getPublicationDate(), bookDto.publicationDate(), "PublicationDate should match"),
                () -> assertNotNull(bookDto.publisher(), "Publisher should not be null"),
                () -> assertEquals(book.getPublisher().getName(), bookDto.publisher().name(), "Publisher name should match"),
                () -> assertEquals(book.getPublisher().getSlug(), bookDto.publisher().slug(), "Publisher slug should match"),
                () -> assertNotNull(bookDto.authors(), "Authors should not be null"),
                () -> assertEquals(2, bookDto.authors().size(), "Authors size should match"),
                () -> assertEquals("Author One", bookDto.authors().get(0).name(), "First author name should match"),
                () -> assertEquals("Author Two", bookDto.authors().get(1).name(), "Second author name should match")
        );
    }

    @Test
    @DisplayName("Test map null Book to BookDto throws BusinessException")
    void toBookDto_NullBook_ThrowsBusinessException() {
        // Arrange
        Book book = null;
        // Act & Assert
        assertThrows(BusinessException.class, () -> BookMapper.getInstance().fromBookToBookDto(book));
    }

    @Test
    @DisplayName("Test map BookEntity to Book")
    void toBook() {
        // Arrange
        var bookEntity = new BookEntity(
                "978-84-376-0494-7",
                "TitleEs",
                "TitleEn",
                "SynopsisEs",
                "SynopsisEn",
                new BigDecimal("20.00"),
                10,
                "cover.jpg",
                LocalDate.of(2020, 1, 1),
                new PublisherEntity("Publisher Name", "publisher-slug"),
                List.of(
                        new AuthorEntity("Author One", "nationality1", "Bio1", "Bio1En", 1970, null, "author-one"),
                        new AuthorEntity("Author Two", "nationality2", "Bio2", "Bio2En", 1980, 2020, "author-two")
                )
        );

        // Act
        var book = BookMapper.getInstance().fromBookEntityToBook(bookEntity);

        // Assert
        assertAll(
                () -> assertEquals(bookEntity.isbn(), book.getIsbn(), "ISBN should match"),
                () -> assertEquals(bookEntity.titleEs(), book.getTitleEs(), "TitleEs should match"),
                () -> assertEquals(bookEntity.titleEn(), book.getTitleEn(), "TitleEn should match"),
                () -> assertEquals(bookEntity.synopsisEs(), book.getSynopsisEs(), "SynopsisEs should match"),
                () -> assertEquals(bookEntity.synopsisEn(), book.getSynopsisEn(), "SynopsisEn should match"),
                () -> assertEquals(bookEntity.basePrice(), book.getBasePrice(), "BasePrice should match"),
                () -> assertEquals(new BigDecimal("18.00"), book.getPrice(), "FinalPrice should match"),
                () -> assertEquals(bookEntity.discountPercentage(), book.getDiscountPercentage(), "DiscountPercentage should match"),
                () -> assertEquals(bookEntity.cover(), book.getCover(), "Cover should match"),
                () -> assertEquals(bookEntity.publicationDate(), book.getPublicationDate(), "PublicationDate should match"),
                () -> assertNotNull(book.getPublisher(), "Publisher should not be null"),
                () -> assertEquals(bookEntity.publisher().name(), book.getPublisher().getName(), "Publisher name should match"),
                () -> assertEquals(bookEntity.publisher().slug(), book.getPublisher().getSlug(), "Publisher slug should match"),
                () -> assertNotNull(book.getAuthors(), "Authors should not be null"),
                () -> assertEquals(2, book.getAuthors().size(), "Authors size should match"),
                () -> assertEquals("Author One", book.getAuthors().get(0).getName(), "First author name should match"),
                () -> assertEquals("Author Two", book.getAuthors().get(1).getName(), "Second author name should match")
        );
    }

En este caso, tendremos que usar Mockito para mockear nuestras dependencias. Por ejemplo, a nuestro servicio de libros necesitamos inyectarle la dependencia con el repositorio:

public class BookServiceImpl implements BookService {

    private final BookRepository bookRepository;

    public BookServiceImpl(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

Por lo tanto, lo primero que haremos en nuestro test es mockear ese repositorio:

@ExtendWith(MockitoExtension.class)
class BookServiceImplTest {

    @Mock
    private BookRepository bookRepository;

    @InjectMocks
    private BookServiceImpl bookServiceImpl;

A partir de ahí, como hemos hecho en los anteriores tests, iremos probando diferentes situaciones:

    @Test
    @DisplayName("getAll should return list of books")
    void getAll_ShouldReturnListOfBooks() {
        // Arrange
        int page = 0;
        int size = 10;

        // Mock books
        BookEntity bookEntity1 = new BookEntity(
                "123",
                "TitleEs1",
                "TitleEn1",
                "SynopsisEs1",
                "SynopsisEn1",
                new BigDecimal("10.00"),
                5,
                "cover1.jpg", LocalDate.of(2020, 1, 1),
                null,
                null
        );
        BookEntity bookEntity2 = new BookEntity(
                "456",
                "TitleEs2",
                "TitleEn2",
                "SynopsisEs2",
                "SynopsisEn2",
                new BigDecimal("15.00"),
                10,
                "cover2.jpg", LocalDate.of(2021, 6, 15),
                null,
                null
        );
        List<BookEntity> bookEntities = List.of(bookEntity1, bookEntity2);
        when(bookRepository.findAll(page, size)).thenReturn(bookEntities);


        // Act
        List<BookDto> result = bookServiceImpl.getAll(page, size);

        // Assert
        assertAll(
                () -> assertNotNull(result, "Result should not be null"),
                () -> assertEquals(2, result.size(), "Result size should be 2"),
                () -> assertEquals("123", result.get(0).isbn(), "First book ISBN should match"),
                () -> assertEquals("456", result.get(1).isbn(), "Second book ISBN should match")
        );

        // Verify interaction with mock
        Mockito.verify(bookRepository).findAll(page, size);
    }


    // test getByIsbn when book exists

    // test getByIsbn when book does not exist

    // test findByIsbn when book exists

    // test findByIsbn when book does not exist

    // test create book

    // test create book with existing isbn

    // test create book with invalid data

    // test create book with non-existing authors

    // .....

Ten en cuenta que éstos son sólo algunos ejemplos de tests. Deberíamos crear nuevos tests por cada funcionalidad o lógica de negocio que añadiéramos al proyecto, como añadir autores, editoriales, sacar el listado de autores de un libro…
  • clase/daw/dws/1eval/domain2.txt
  • Última modificación: 2025/09/17 10:18
  • por cesguiro