Testes de Integração no Spring Boot: H2 vs @MockBean com exemplos reais

1. Introdução Se você chegou até aqui, provavelmente quer entender como fazer testes de integração com Spring Boot, usando @MockBean ou um banco de dados em memória como o H2. A diferença entre essas abordagens é simples: Com @MockBean, você simula componentes específicos, retornando valores pré-definidos. Com H2, você executa os testes em um banco em memória que simula o comportamento do banco real. Antes de seguir, é importante entender duas anotações do Spring Boot: @WebMvcTest: ideal para testes unitários de controllers, sem carregar todo o contexto da aplicação. @SpringBootTest: carrega o contexto completo da aplicação, sendo o que usaremos aqui. Observações importantes: o projeto evita o uso de @Service, @Repository e @Autowired , a injeção de dependência é feita de forma manual; há links no fim do artigo explicando os motivos para evitar as notações citadas e sobre os tipos de testes. Descubra como escrever testes de integração eficazes com Spring Boot usando duas abordagens poderosas: banco de dados em memória com H2 e simulações com @MockBean. Nesta postagem, será mostrado como configurar cada cenário com exemplos reais, boas práticas e dicas práticas para evitar armadilhas comuns. Testar nunca foi tão direto ao ponto. Então, vamos lá!? 2. Visão Geral do Projeto 2.1. Dependências do Maven (pom.xml) O pom.xml está configurado com dependências para Spring Boot, JPA, PostgreSQL, H2, Lombok e bibliotecas de teste. 4.0.0 org.springframework.boot spring-boot-starter-parent 3.4.4 com.javawebtest java-web-test 0.0.1-SNAPSHOT java-web-test Project to test anything about java web 17 org.springframework.boot spring-boot-starter-data-jdbc org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-docker-compose runtime true com.h2database h2 runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test io.projectreactor reactor-test test org.postgresql postgresql runtime org.apache.maven.plugins maven-compiler-plugin org.projectlombok lombok org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok org.apache.maven.plugins maven-compiler-plugin 3.8.0 org.projectlombok lombok ${lombok.version} org.projectlombok lombok-mapstruct-binding 0.2.0 -Amapstruct.defaultComponentModel=spring Nota: verifique as configurações do plugin do Lombok caso a aplicação não inicie corretamente. 2.2. Configuração dos @Bean O projeto utiliza uma classe com @Configuration para definir os @Bean principais: @Configuration public class ProductConfiguration { public static final String PRODUCT_REPOSITORY_BEAN = "productRepositoryBean"; public static final String PRODUCT_SERVICE_BEAN = "productServiceBean"; @Profile({"!test"}) @Bean(PRODUCT_REPOSITORY_BEAN) public ProductRepository productRepository(ProductJpaRepository productJpaRepository) { return new ProductRepositoryImpl(productJpaRepository); } @Bean(PRODUCT_SERVICE_BEAN) public ProductService productService(@Qualifier(PRODUCT_REPOSITORY_BEAN) ProductRepository productRepository) { return new ProductServiceImpl(productRepository);

Apr 23, 2025 - 20:40
 0
Testes de Integração no Spring Boot: H2 vs @MockBean com exemplos reais

1. Introdução

Se você chegou até aqui, provavelmente quer entender como fazer testes de integração com Spring Boot, usando @MockBean ou um banco de dados em memória como o H2.

A diferença entre essas abordagens é simples:

  • Com @MockBean, você simula componentes específicos, retornando valores pré-definidos.
  • Com H2, você executa os testes em um banco em memória que simula o comportamento do banco real.

Antes de seguir, é importante entender duas anotações do Spring Boot:

  • @WebMvcTest: ideal para testes unitários de controllers, sem carregar todo o contexto da aplicação.
  • @SpringBootTest: carrega o contexto completo da aplicação, sendo o que usaremos aqui.

Observações importantes:

  • o projeto evita o uso de @Service@Repository e @Autowired , a injeção de dependência é feita de forma manual;
  • há links no fim do artigo explicando os motivos para evitar as notações citadas e sobre os tipos de testes.

Descubra como escrever testes de integração eficazes com Spring Boot usando duas abordagens poderosas: banco de dados em memória com H2 e simulações com @MockBean. Nesta postagem, será mostrado como configurar cada cenário com exemplos reais, boas práticas e dicas práticas para evitar armadilhas comuns. Testar nunca foi tão direto ao ponto.

Então, vamos lá!?

2. Visão Geral do Projeto

2.1. Dependências do Maven (pom.xml)

pom.xml está configurado com dependências para Spring Boot, JPA, PostgreSQL, H2, Lombok e bibliotecas de teste.


 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        3.4.4
         
    
    com.javawebtest
    java-web-test
    0.0.1-SNAPSHOT
    java-web-test
    Project to test anything about java web
    
        17
    
    
        
            org.springframework.boot
            spring-boot-starter-data-jdbc
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.springframework.boot
            spring-boot-docker-compose
            runtime
            true
        
        
            com.h2database
            h2
            runtime
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            io.projectreactor
            reactor-test
            test
        
        
            org.postgresql
            postgresql
            runtime
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.0
                
                    
                        
                            org.projectlombok
                            lombok
                            ${lombok.version}
                        
                        
                            org.projectlombok
                            lombok-mapstruct-binding
                            0.2.0
                        
                    
                    
                        
                            -Amapstruct.defaultComponentModel=spring
                        
                    
                
            
        
    


Nota: verifique as configurações do plugin do Lombok caso a aplicação não inicie corretamente.

2.2. Configuração dos @Bean

O projeto utiliza uma classe com @Configuration para definir os @Bean principais:

@Configuration
public class ProductConfiguration {

    public static final String PRODUCT_REPOSITORY_BEAN = "productRepositoryBean";
    public static final String PRODUCT_SERVICE_BEAN = "productServiceBean";

    @Profile({"!test"})
    @Bean(PRODUCT_REPOSITORY_BEAN)
    public ProductRepository productRepository(ProductJpaRepository productJpaRepository) {
        return new ProductRepositoryImpl(productJpaRepository);
    }

    @Bean(PRODUCT_SERVICE_BEAN)
    public ProductService productService(@Qualifier(PRODUCT_REPOSITORY_BEAN) ProductRepository productRepository) {
        return new ProductServiceImpl(productRepository);
    }
}

Atenção a notação @Profile({"!test"}), aqui é informado que quando o profile a ser executado contiver o valor teste esse @Bean deverá ficar sem ser ativado. Isso é importante para exemplificar o ponto aqui a ser passado.

2.3. Estrutura de Classes

O projeto possui:

  • Product : entidade
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "product")
@Table(name = "product")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "creation_date")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
    private LocalDateTime creationDate;

}
  • ProductController: API Rest
@EnableWebMvc
@RestController
@RequestMapping(value = "/product")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping(value = {"", "/"},
            produces = {MediaType.APPLICATION_JSON_VALUE},
            consumes = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Product> save (@RequestBody Product product) {

        product = productService.save(product);

        return ResponseEntity.status(HttpStatus.CREATED).body(product);
    }
}
  • ProductService: lógica de negócio
public interface ProductService {
    Product save(Product product);
}

public class ProductServiceImpl implements ProductService {

    private final ProductRepository productRepository;

    public ProductServiceImpl(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Override
    public Product save(Product product) {

        product = productRepository.save(product);

        return product;
    }
}
  • ProductRepository: camada de persistência
public interface ProductJpaRepository extends JpaRepository<Product, Long> {
}

public interface ProductRepository {
    Product save(Product product);
}

public class ProductRepositoryImpl implements ProductRepository {

    private final ProductJpaRepository productJpaRepository;

    public ProductRepositoryImpl(ProductJpaRepository productJpaRepository) {
        this.productJpaRepository = productJpaRepository;
    }

    @Override
    public Product save(Product product) {

        product = productJpaRepository.save(product);

        return product;
    }
}

2.4. Fluxo da Aplicação

O fluxo é Controller → Service → Repository → Banco de Dados.

Image description

2.5. application.yml (ambiente dev)

Quando se trabalha com Springboot uma das formas de se configurar as variáveis do projeto é através do arquivo application.yml, sendo assim, a configuração do banco de dados também fica neste arquivo, segue um exemplo da configuração do projeto.

spring:
  config:
    activate:
      on-profile: dev

  jpa:
    defer-datasource-initialization: true
    show-sql: true

  datasource:
    url: jdbc:postgresql://localhost:5432/test
    username: test
    password: test1234
    driverClassName: org.postgresql.Driver

server:
  port: 8080

3. Teste de Integração com H2

Ao realizar testes, é comum substituir o banco real por um banco em memória como o H2. Para isso:

3.1. application.yml para testes

Arquivo localizado em src/test/resources/:

spring:
  config:
    activate:
      on-profile: test

  jpa:
    defer-datasource-initialization: true
    show-sql: true
    hibernate:
      ddl-auto: create-drop

  h2:
    console:
      enabled: true

  datasource:
    url: jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    driverClassName: org.h2.Driver

3.2. Configuração dos @Bean para teste

Assim como em produção os @Bean deverão ser mapeados, no caso em tela, o @Bean relativo ao banco de dados deverá ser mapeado também. Repare que a notação mudou de @Configuration para @TestConfiguration.

@TestConfiguration
public class ConfigTest {

    @Profile({"test"})
    @Bean(ProductConfiguration.PRODUCT_REPOSITORY_BEAN)
    public ProductRepository productRepository(ProductJpaRepository productJpaRepository) {
        return new ProductRepositoryImpl(productJpaRepository);
    }
}

Atenção a notação @Profile({"test"}), aqui é informado que quando o profile a ser executado contiver o valor teste esse @Bean deverá ser ativado. Isso é importante para exemplificar o ponto aqui a ser passado.

3.3. O Teste

O ponto mais esperado chegou, o momento de criar o teste e vê-lo funcionar.

@ActiveProfiles({"test"})
@SpringBootTest(classes = {ConfigTest.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ProductControllerH2Test {

    @Autowired
    MockMvc mockMvc;

    ObjectMapper objectMapper;

    @BeforeEach
    void beforeEach() {
        objectMapper = new ObjectMapper();
    }

    @Test
    public void test() throws Exception {
        String body = objectMapper.writeValueAsString(Product.builder()
                .name("new product")
                .build());

        mockMvc.perform(MockMvcRequestBuilders.post("/product/")
                        .content(body)
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("new product"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));
    }
}

Neste ponto é importante observar algumas notações, tais quais @ActiveProfiles({"test"}) e
@SpringBootTest . A notação @ActiveProfiles({"test"}) indica que para executar os testes desta classe deverá ser ativado o profile test, desta forma, excluindo outras configurações. Sobre a notação @SpringBootTest foi explicado anteriormente sobre a sua função, no entanto, tem algo especificado dentro dela que é importante, que é classes = {ConfigTest.class}, isso significa que a classe ConfigTest.class deverá ser carregada no contexto do Spring quando o mesmo for inicializado.

Repare que inexiste notações de mock para os @Bean do projeto, isso ocorreu pois de fato nada está sendo “mockado”, o fluxo ocorrerá do início ao fim, porém, o banco de dados utilizado será o H2 ao invés do banco utilizado em prod, dev, homolog ou algo do tipo que poderia ser um MySQL, PostgreSQL, Oracle ou outro.

4. Teste de Integração com @MockBean

Aqui, ao invés de usar um banco em memória, utilizamos mocks para simular o comportamento do repositório.

@ActiveProfiles({"test"})
@SpringBootTest(classes = {ConfigTest.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ProductControllerMockTest {

    @Autowired
    MockMvc mockMvc;

    ObjectMapper objectMapper;

    @MockBean
    ProductJpaRepository productJpaRepository;

    @BeforeEach
    void beforeEach() {
        objectMapper = new ObjectMapper();
    }

    @Test
    public void test() throws Exception {
        Product product = Product.builder()
                .name("new product")
                .build();

        Mockito.when(productJpaRepository.save(product))
                .thenReturn(Product.builder()
                        .id(1L)
                        .name("test mock productJpaRepository")
                        .build());

        String body = objectMapper.writeValueAsString(product);

        mockMvc.perform(MockMvcRequestBuilders.post("/product/")
                        .content(body)
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("test mock productJpaRepository"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));
    }
}

Atenção: o @MockBean precisa ser usado no ponto certo do fluxo. Mockar classes fora da cadeia de execução esperada pode quebrar a injeção de dependências.

5. Quando usar H2 ou @MockBean?

Critério H2 (Banco em Memória) @MockBean (Simulação de Dependências)
Fidelidade com banco real ✅ Alta (simula o banco de verdade com SQL real) ❌ Baixa (lógica simulada, sem persistência real)
Performance ❌ Mais lento (carrega contexto + banco em memória) ✅ Rápido (sem overhead de banco)
Cobertura de integração ✅ Completa (inclui JPA, transações, etc.) ❌ Limitada (testa só partes isoladas)
Manutenção de testes ⚠️ Média (precisa manter schema e dados consistentes) ✅ Simples (mocka só o necessário)
Diagnóstico de bugs reais ✅ Melhor (simula situações reais de persistência) ❌ Pode mascarar bugs que só aparecem com DB real
Complexidade de setup ❌ Alta (requires profile, config H2, beans específicos) ✅ Baixa (só anotar com @MockBean e boa)
Indicado para testes de...

This site uses cookies. By continuing to browse the site you are agreeing to our use of cookies.