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.

ZdQMKmxKk9mlD-AzNuUZpQxdCZ4VSi4ELW6d

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:

XNRt3lBVu-47pwdDQOg73BhX-Fy3OhCr-mZf

Quando executamos o teste, vemos que ele passa:

Gx1sVQbUTi3rn7DiqgGQuMUVN60-4xWHIHji

Como testamos nosso serviço?

Agora, vamos testar o UserService. Ele é bem simples de testar.

5ALGlLPT49JTxgUal22WbDbfrEhjBHD24eSV

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.

HhoeIyT6oUhvYVcCD2nbZepI3S-qbPfhUE5n

Antes de consertarmos, vamos escrever um teste que satisfaça essa regra.

VZqkwBqS14vzxKBcMWdtHPES1xo8XxK3ht3I

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:

hFTPEmxhpDT6WVsWc3VpeQ0Y9XOGwBdwr05u

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:

ZvTQXeFLK6VNU5jrP7Xl61vbBwNi6wJMJnHR

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=?
817OJk5EOmr4TjTvg9Pcul7Ki6fdpNMqbYqP

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.