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);

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.
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
.
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.