I was looking if there is a way with some kind of annotation rather than with code to clean the database after a test generated registries in it. I found about @Dirtiescontext
but it doesn't seem to be working. I don't know if I'm not using it right or what the problem is. I am using Spring Boot 3 and MySQL 8.
This is my test code:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
@DisplayName("Articles Integration Tests")
public class ArticlesControllerIT {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ArticlesService articlesService;
@Autowired
private ModelMapper modelMapper;
@Autowired
private ArticleRepository articleRepository;
@Test
@Sql("/create_suppliers.sql")
@DisplayName("Happy path: Create articles")
void WHEN_calling_create_articles_THEN_status_is_200() {
final List<ArticleDTO> articleDTOS = Instancio.ofList(ArticleDTO.class).size(3)
.ignore(field(ArticleDTO::getId))
.ignore(field(ArticleDTO::getCategory))
.supply(all(SupplierDTO.class), random -> SupplierDTO.builder()
.id(random.intRange(1, 10))
.build())
.create();
final CreateArticlesRequest createArticlesRequest = CreateArticlesRequest.builder()
.articles(articleDTOS)
.user(Instancio.create(String.class))
.build();
final ResponseEntity<CreateArticlesResponse> response = restTemplate.exchange(restTemplate.getRootUri() + URIConstants.ARTICLES_URI,
HttpMethod.POST, new HttpEntity<>(createArticlesRequest), new ParameterizedTypeReference<>() {});
final List<ArticleDTO> articles = Objects.requireNonNull(response.getBody()).getArticles();
//Remove ID from the response articicles to check that content is the same as the articles to be created
articles.forEach(article -> article.setId(null));
assertEquals(articles.size(), articleDTOS.size() , "articles.size() should be " + articleDTOS.size());
assertTrue(articles.containsAll(articleDTOS), "articles should contain the same elements as articulosDTOS");
assertEquals(HttpStatus.OK, response.getStatusCode(), "Status Code should be " + HttpStatus.OK);
}
}
My service method that creates the registries:
@Service
public class ArticlesServiceImpl implements ArticlesService {
private final ArticleRepository articleRepository;
private final ModelMapper modelMapper;
private final SuppliersService suppliersService;
@Autowired
ArticlesServiceImpl(final ArticleRepository articleRepository, final ModelMapper modelMapper, final SuppliersService suppliersService) {
this.articleRepository = articleRepository;
this.modelMapper = modelMapper;
this.suppliersService = suppliersService;
}
@Override
public List<Article> createArticles(final List<Article> articles, final String usuario) {
final List<ArticleDAO> articleDAOS;
this.suppliersService.checkSuppliersExist(articles
.stream()
.map(Article::getSupplier)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
this.checkArticlesDontExistByName(articles);
articles.forEach(article -> article.setUser(usuario));
articleDAOS = this.modelMapper.map(articles, new TypeToken<List<ArticleDAO>>(){}.getType());
this.articleRepository.saveAll(articleDAOS);
return this.modelMapper.map(articleDAOS, new TypeToken<List<Article>>(){}.getType());
}
}
My repository:
@Repository
public interface ArticleRepository extends JpaRepository<ArticleDAO, Integer> {
boolean existsByName(final String name);
}
My entity:
@Getter
@Setter
@NoArgsConstructor
@Entity
@EqualsAndHashCode
@Table(name = "article")
public class ArticleDAO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "selling_price")
private BigDecimal sellingPrice;
@Column(name = "supplier_price")
private BigDecimal supplierPrice;
@Column(name = "user")
private String user;
@Column(name = "selling_unit_type")
private String sellingUnitType;
@Column(name = "inventory_amount")
private Integer inventoryAmount;
@Column(name = "picture")
private String picture;
@Column(name = "color")
private String color;
@Column(name = "reference")
private String reference;
@Column(name = "bar_code")
private String barCode;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "category_id", referencedColumnName = "id")
private CategoryDAO category;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "supplier_id", referencedColumnName = "id")
private SupplierDAO supplier;
}
Am I using @DirtiesContext
wrong, or this is just not the way?
Whether the @DirtiesContext
annotation will clean persisted data in the test database depends on how you create this database in the test.
If you are using embedded H2 in-memory database, then @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
will cause Spring to discard the ApplicationContext and create a new one after each test method, including the destruction and re-creation of the H2 in-memory database, so it effectively "cleans" the database state between tests. But if you create your test database, for example, using Testcontainers, then @DirtiesContext
will not necessarily affect the test container with the database.
I would recommend using SQL scripts to clean up the database after each test with the @Sql
annotation, just like you do to create test data:
@Sql(scripts = "/truncateTables.sql", executionPhase = AFTER_TEST_METHOD)
From my point of view, this is the most reliable method.
There is also an option to use @Transactional
annotation to rollback test transaction after each test, but this option will not work for you due to loading the real web environment using SpringBootTest.WebEnvironment.RANDOM_PORT
. More info about it in the documentation:
If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.