3

I'm trying to learn some new things using Quarkus, and I kinda got stuck on something: I want to create some tests that are self sustained, I mean, I should be able to run each one independently of each other. I'm using Testcontainers(PostgreSQL) to run some component tests, but I would like to each @Test be able to run on a 'clean database', but I don't think that stoping the container and starting it once again for each @Test would be a good idea. I could have a @BeforeEach(container.dropColumn()) or @BeforeEach(container.truncateColumn()) but: 1 - I don't know if this is the better way, 2 - I don't know how to do this.

Given an hypothetical test scenario:

Scenario 1 - Pass

  • Register new User
  • Find User by its id
  • assert(one_user_on_db)

Scenario 2 - Fail

  • Try to Register new User with invalid data
  • Find all users
  • assert(zero_users_on_db)

The second test fail because the data from the first scenario is still on the database. Here, some code to help.

resources

    @Inject
    UserService userService;
    
    @POST
    @Transactional
    public Response saveUser(@Valid UserDto user){
        return Response.status(Response.Status.CREATED)
                .entity(userService.saveUser(user, user.getPassword())).build();
    }
    
    @GET
    public Iterable<User> findAll(){
        return userService.findAll();
    }

tests

@Test
void shouldSuccessfullyCreateUser(){
    User mockUser = MockUser.onlyMandatoryFields() //MockUser

    given()
            .body(mockUser)
            .contentType(ContentType.JSON)
            .when()
            .post("/users").prettyPeek()
            .then()
            .statusCode(Response.Status.CREATED.getStatusCode());

@Test
void shouldHaveAnEmptyDatabase(){
    User[] userList = given()
            .contentType(ContentType.JSON)
            .when()
            .get("/users").prettyPeek()
            .then()
            .extract()
            .response()
            .as(User[].class);

    assertEquals(0, userList.length);
}

I've already tried @TestTransaction as described in Quarkus Docs but no success.

I was looking for something like @DirtiesContext from Spring.

Anyway, I've got an open repository, if you want to look further into the code. The tests can be found here.

Johnnes Souza
  • 361
  • 1
  • 8
  • 22
  • `@TestTransaction` seems to be flakey - it doesn't rollback sequences for example. I am getting errors like `Duplicate entry '4-2' for key 'PRIMARY'` – jimmy_terra Apr 04 '21 at 13:23

2 Answers2

4

@TestTransaction works properly when you test repositories or CDI beans directly.

I've already tried @TestTransaction as described in Quarkus Docs, but no success.

It works if tests run in the same transaction context as the tested code. Your REST resource works under a different transaction context than your test method; therefore, @TestTransaction will not work in your case. In your case the transaction gets committed at the end of the rest call; hence you cannot rollback it.

See an example of a working test validating the repository directly.

@Test
@TestTransaction
void shouldFail_whenCreatingNewLedgerWithUnrecognizedType() {
    //when/then
    assertThatThrownBy(() -> customSqlRepository.insertWithUnrecognizedType())
            .isInstanceOf(PersistenceException.class)
            .hasMessageContaining("Check constraint violation")
            .hasMessageContaining("LEDGER_ACCOUNT_TYPE IN(");
}
Ole Pannier
  • 3,208
  • 9
  • 22
  • 33
Marek Żylicz
  • 429
  • 3
  • 8
0

A similar effect to the Spring DirtiestContext could be achieved using Quarkus @QuarkusTestProfile, described as:

If a test has a different profile to the previously run test, then Quarkus will be shut down and started with the new profile before running the tests. This is obviously a bit slower, as it adds a shutdown/startup cycle to the test time but gives a great deal of flexibility. I think it might work for you but will be slow.

Consider other solutions

  1. Write an integration test annotated by @TestTransaction validating a service class plus write a test validating your REST controller with the mock of the service injected
  2. Truncate the database before each test,
  3. Create a REST call that will delete the created user and call it from your tests,
Marek Żylicz
  • 429
  • 3
  • 8