Artigo original: How to test services, endpoints, and repositories in Spring Boot
Escrito por: Emre Savcı
Neste artigo, mostrarei como escrever testes unitários em aplicações do SpringBoot.
A razão da necessidade de escrever testes unitários pede um outro artigo para explicar. Para uma breve explicação, no entanto, vou contar a você algumas coisas.
Normalmente, eu defendo o argumento de que um código sem testes unitários é um código morto. Isso porque, quando um desenvolvedor adiciona uma nova funcionalidade a um código que não é coberta por testes unitários, fica propenso a sobrescrever alguma regra de negócio (o que mata o código que foi escrito anteriormente). Talvez não fique exatamente propenso, mas você consegue imaginar que podem acontecer erros quando um projeto precisar ser modificado. Testes unitários são a única forma de proteger seu código contra quebras que podem ocorrer em mudanças.
Por que testes unitários em enpoints?
Toda vez que escrevemos um endpoint, precisamos ter certeza de que algumas coisas estão funcionando corretamente. O endpoint deve retornar os dados em uma estrutura correta e lidar com a requisição corretamente. Podemos testar manualmente, o que não é preferível. Então, escrevemos testes unitários para garantir que nossos endpoints funcionam corretamente. Existe também uma forma de testar endpoints usando testes automatizados, mas não é o objetivo deste artigo.
Por que testes unitário em serviços?
Isso já deveria ser claro, mas, em todo caso: precisamos ter certeza que nossa regra de negócio funciona corretamente.
Por que testes unitários em repositórios?
Existem alguns casos para testar repositórios. É claro que não testamos o próprio framework. Porém, escrevemos testes unitários para ter a certeza de que nossas especificações ou relações foram implementadas corretamente.
Como testamos controllers?
Agora é a hora de mostrar a você como testar nossos controllers no SpringBoot. Vamos imaginar que escrevemos uma aplicação que nos permite salvar usuários no nosso banco de dados. Definimos uma entidade user (usuário), um serviço para o usuário e um controller.
Observação: os exemplos demonstrados nesse artigo não são para arquiteturas de aplicações reais em produção.
@Data@Entitypublic class User { @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator") @Column(name = "id", columnDefinition = "BINARY(16)") private UUID id; private String name; private String email; private int age;}
@Datapublic class CreateUserRequest { private String name; private String email; private int age;}
@RestController@RequestMapping("/users")public class UserController { UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @PostMapping public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) { User created = userService.save(request); return ResponseEntity.ok(created); }}
Nossos controllers tem uma dependência no UserService, mas não estamos interessados no que o serviço faz agora.
Agora vamos escrever um teste unitário para nosso controller e garantir que ele funciona corretamente.
Simulamos nosso serviço porque ainda não temos sua implementação em detalhes. Testamos nosso controller apenas aqui. Usamos o MockMvc
para testar nosso controller e mapear nosso objeto para fins de serialização.
Configuramos o método userService.Save()
para retornar nosso objeto user desejado. Passamos uma requisição para nosso controller e depois verificamos os dados retornados na seguinte linha:
andExpect(jsonPath("$.name").value(request.getName()))
.
Também temos outros métodos que podem ser usados (documentação do Spring Boot, em inglês). Aqui está a lista:
Quando executamos o teste, vemos que ele passa:
Como testamos nosso serviço?
Agora, vamos testar o UserService. Ele é bem simples de testar.
Simulamos um repositório e injetamos nele um UserService. Agora, quando executarmos o teste, veremos que ele passa.
Vamos adicionar uma regra de negócio ao UserService: digamos que o usuário precisa ter um endereço de e-mail.
Vamos mudar nosso método de salvamento no UserService:
public User save(CreateUserRequest request) { requireNonNull(request.getEmail()); User user = new User(); user.setName(request.getName()); user.setEmail(request.getEmail()); user.setAge(request.getAge()); userRepository.save(user); return user;}
Quando executamos o teste novamente, veremos que o teste falha.
Antes de consertarmos, vamos escrever um teste que satisfaça essa regra.
Escrevemos um novo teste que especificou que, se enviarmos um e-mail nulo, ele lançará uma NullPointerException.
Vamos agora consertar o teste adicionando um e-mail à nossa requisição:
createUserRequest.setEmail("testemail");
Executando ambos os testes:
Como testamos nossos repositórios?
Agora, vamos testar nossos repositórios. Vamos usar um banco de dados h2 em memória com TestEntityManager
.
Nosso repositório é definido assim:
@Repositorypublic interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> { Optional<User> findById(UUID id);}
Primeiro, configuramos o h2db. Criamos um arquivo chamado application.yaml em test -> resources:
spring: application: name: Spring Boot Rest API datasource: type: com.zaxxer.hikari.HikariDataSource url: "jdbc:h2:mem:test-api;INIT=CREATE SCHEMA IF NOT EXISTS dbo\\;CREATE SCHEMA IF NOT EXISTS definitions;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MSSQLServer" name: password: username: initialization-mode: never hikari: schema: dbo jpa: database: H2 database-platform: org.hibernate.dialect.H2Dialect show-sql: true hibernate: ddl-auto: create-drop test: database: replace: none
Em seguida, escrevemos o primeiro teste básico para nosso repositório: salvar um usuário e recuperá-lo:
@RunWith(SpringRunner.class)@DataJpaTestpublic class UserRepositoryTest { @Autowired TestEntityManager entityManager; @Autowired UserRepository sut; @Test public void it_should_save_user() { User user = new User(); user.setName("test user"); user = entityManager.persistAndFlush(user); assertThat(sut.findById(user.getId()).get()).isEqualTo(user); }}
Quando executamos veremos algumas saídas no console, e nosso teste também passa:
Agora, vamos adicionar outro método ao nosso repositório para buscar um usuário através do seu e-mail:
Optional<User> findByEmail(String email);
Escrevemos, por fim, outro teste:
@Testpublic void it_should_find_user_byEmail() { User user = new User(); user.setEmail("testmail@test.com"); user = entityManager.persistAndFlush(user); assertThat(sut.findByEmail(user.getEmail()).get()).isEqualTo(user);}
Quando damos uma olhada no console depois de executar o teste, vemos o SQL gerado pelo hibernate:
SELECT user0_.id AS id1_1_,user0_.age AS age2_1_,user0_.email AS email3_1_,user0_.name AS name4_1_FROM user user0_WHERE user0_.email=?
Até agora, tudo bem. Cobrimos o básico de testes unitários com o SpringBoot.
Agora, não temos mais desculpas para não escrever testes unitários! Espero que tenha sido claro para você como escrever testes unitários para cada tipo de propósito.