Aqui está o código fonte do WidgetServiceTest aula:
package com.infoworld.widgetservice.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import java.util.Optional;
import com.infoworld.widgetservice.model.Widget;
import com.infoworld.widgetservice.repository.WidgetRepository;
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;
@ExtendWith(MockitoExtension.class)
public class WidgetServiceTest {
@Mock
private WidgetRepository repository;
@InjectMocks
private WidgetService service;
@Test
void testFindById() {
Widget widget = new Widget(1L, "My Widget", 1);
when(repository.findById(1L)).thenReturn(Optional.of(widget));
Optional w = service.findById(1L);
assertTrue(w.isPresent());
assertEquals(1L, w.get().getId());
assertEquals("My Widget", w.get().getName());
assertEquals(1, w.get().getVersion());
}
}
JUnit 5 suporta extensões e Mockito definiu uma extensão de teste que podemos acessar através do @ExtendWith anotação. Esta extensão permite que o Mockito leia nossa classe, encontre objetos para simular e injete simulações em outras classes. O WidgetServiceTest diz a Mockito para criar uma simulação WidgetRepositoryanotando-o com o @Mock anotação e, em seguida, injetar essa simulação no WidgetServiceusando o @InjectMocks anotação. O resultado é que temos um WidgetService que podemos testar e terá uma simulação WidgetRepository que podemos configurar para nossos casos de teste.
Veja também: Testes unitários avançados com JUnit 5, Mockito e Hamcrest.
Este não é um teste abrangente, mas deve ajudá-lo a começar. Tem um único método, testFindById()que demonstra como testar um método de serviço. Isso cria uma simulação Widget instância e então usa o Mockito when() método, assim como usamos no teste do controlador, para configurar o WidgetRepository para devolver um Optional disso Widget quando é findById() método é chamado. Então ele invoca o WidgetServicede findById() método e valida que a simulação Widget é retornado.
Fatia testando um repositório Spring Data JPA
A seguir, testaremos nosso repositório JPA (WidgetRepository.java), mostrado aqui:
package com.infoworld.widgetservice.repository;
import java.util.List;
import com.infoworld.widgetservice.model.Widget;
import org.springframework.data.jpa.repository.JpaRepository;
public interface WidgetRepository extends JpaRepository {
List findByName(String name);
}
O WidgetRepository é um repositório Spring Data JPA, o que significa que definimos a interface e o Spring gera a implementação. Ele estende o JpaRepository interface, que aceita dois argumentos:
- O tipo de entidade que persiste, ou seja, um
Widget. - O tipo de chave primária, que neste caso é um
Long.
Ele gera implementações de métodos CRUD comuns para criarmos, atualizarmos, excluirmos e localizarmos widgets, e então podemos definir nossos próprios métodos de consulta usando uma convenção de nomenclatura específica. Por exemplo, definimos um findByName() método que retorna um List de WidgetS. Porque “name”é um campo em nosso Widget entidade, o Spring irá gerar uma consulta que encontra todos os widgets com o nome especificado.
Aqui está o nosso WidgetRepositoryTest aula:
package com.infoworld.widgetservice.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.infoworld.widgetservice.model.Widget;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
@DataJpaTest
public class WidgetRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private WidgetRepository widgetRepository;
private final List widgetIds = new ArrayList<>();
private final List testWidgets = Arrays.asList(
new Widget("Widget 1", 1),
new Widget("Widget 2", 1),
new Widget("Widget 3", 1)
);
@BeforeEach
void setup() {
testWidgets.forEach(widget -> {
entityManager.persist(widget);
widgetIds.add((Long)entityManager.getId(widget));
});
entityManager.flush();
}
@AfterEach
void teardown() {
widgetIds.forEach(id -> {
Widget widget = entityManager.find(Widget.class, id);
if (widget != null) {
entityManager.remove(widget);
}
});
widgetIds.clear();
}
@Test
void testFindAll() {
List widgetList = widgetRepository.findAll();
assertEquals(3, widgetList.size());
}
@Test
void testFindById() {
Widget widget = widgetRepository.findById(
widgetIds.getFirst()).orElse(null);
assertNotNull(widget);
assertEquals(widgetIds.getFirst(), widget.getId());
assertEquals("Widget 1", widget.getName());
assertEquals(1, widget.getVersion());
}
@Test
void testFindByIdNotFound() {
Widget widget = widgetRepository.findById(
widgetIds.getFirst() + testWidgets.size()).orElse(null);
assertNull(widget);
}
@Test
void testCreateWidget() {
Widget widget = new Widget("New Widget", 1);
Widget insertedWidget = widgetRepository.save(widget);
assertNotNull(insertedWidget);
assertEquals("New Widget", insertedWidget.getName());
assertEquals(1, insertedWidget.getVersion());
widgetIds.add(insertedWidget.getId());
}
@Test
void testFindByName() {
List found = widgetRepository.findByName("Widget 2");
assertEquals(1, found.size(), "Expected to find 1 Widget");
Widget widget = found.getFirst();
assertEquals("Widget 2", widget.getName());
assertEquals(1, widget.getVersion());
}
}
O WidgetRepositoryTest classe é anotada com o @DataJpaTest anotação, que é uma anotação de teste de fatia que carrega repositórios e entidades no contexto do aplicativo Spring e cria um TestEntityManager que podemos conectar automaticamente em nossa classe de teste. O TestEntityManager nos permite realizar operações de banco de dados fora de nosso repositório para que possamos configurar e desmontar nossos cenários de teste.
No WidgetRepositoryTest classe, nós autowire em ambos os nossos WidgetRepository e TestEntityManager. Então, definimos um setup() método que é anotado com o JUnit @BeforeEach anotação, então ela será executada antes cada caso de teste é executado. A seguir, definimos um teardown() método que é anotado com o JUnit @AfterEach anotação, então ela será executada depois cada teste é concluído. A classe define um testWidgets lista que contém três widgets de teste e, em seguida, o setup() método insere aqueles no banco de dados usando o TestEntityManagerde persist() método. Após inserir cada widget, ele salva o ID gerado automaticamente para que possamos referenciá-lo em nossos testes. Finalmente, depois de persistir os widgets, ele os libera no banco de dados chamando o método TestEntityManagerde flush() método. O teardown() método itera sobre tudo Widget IDs, encontra o Widget usando o TestEntityManagerde find() método e, se for encontrado, remove-o do banco de dados. Finalmente, ele limpa a lista de IDs do widget para que o setup() método pode reconstruí-lo para o próximo teste. (Observe que o TestEntityManager remove entidades diretamente; não tem um remover por ID método, então primeiro temos que encontrar cada Widget e remova-os um por um.)
Embora a maioria dos métodos testados sejam gerados automaticamente e bem testados, eu queria demonstrar como escrever vários tipos de testes. O único método que realmente precisamos testar é o findByName() método porque esse é o único método personalizado que definimos. Por exemplo, se definissemos o método como findByNam() em vez de findByName()então o método não funcionaria, então definitivamente vale a pena testar.
Conclusão
Spring fornece suporte robusto para testar cada camada de um aplicativo Spring MVC. Neste artigo, revisamos como testar controladores usando MockMvc; serviços, utilizando a extensão JUnit Mockito; e repositórios, usando o Spring TestEntityManager. Também revisamos os testes de fatia como uma estratégia para reduzir a utilização de recursos de teste e minimizar o tempo necessário para executá-los. O teste de fatia é implementado no Spring usando o @WebMvcTest e @DataJpaTest anotações. Espero que esses exemplos tenham fornecido tudo que você precisa para se sentir confortável escrevendo testes robustos para seus aplicativos Spring MVC.
