Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction not rollbacked by using Rest Assured with transactionMode = SINGLE_TRANSACTION #975

Open
BagasAjah opened this issue Mar 13, 2024 · 4 comments

Comments

@BagasAjah
Copy link

BagasAjah commented Mar 13, 2024

Expected Behavior

I'd expect the data to be rolled back if a Micronaut test is annotated like @MicronautTest(transactionMode = TransactionMode.SINGLE_TRANSACTION). Even we are using rest assured to test the endpoint.

Actual Behaviour

The data is not rolled back. And probably would be polluted in the next test scenario.

Steps To Reproduce

BookController.java

@Controller("/book")
@AllArgsConstructor
public class BookController {
    private final BookRepository bookRepository;
    
    @Get("/")
    public List<Book> getAll() {
        return bookRepository.findAll();
    }


    @Post("/")
    public Book save() {
        Book book = new Book("Dummy Name");
        bookRepository.save(book);
        return book;
    }
}

BookControllerTest.java

@MicronautTest(transactionMode = TransactionMode.SINGLE_TRANSACTION)
public class BookControllerTest {

    @Inject
    RequestSpecification spec;

    @BeforeEach
    void init() {
        spec.given()
                .when()
                .post("/book")
                .then()
                .statusCode(200);

        // Configure `RequestSpecification` to update re-declared params to reuse it in all tests as documented at
        // https://github.com/rest-assured/rest-assured/wiki/Usage#param-config
        spec.given().config(config().paramConfig(paramConfig().replaceAllParameters()));
    }

    @Test
    void firstInsert() {
        List<VoucherDTO> list = spec.given().when()
                .get("/book")
                .then()
                .statusCode(200)
                .extract()
                .as(List.class);
        assertEquals(1, list.size());
    }

    @Test
    void secondInsert() {
        List<VoucherDTO> list = spec.given().when()
                .get("/book")
                .then()
                .statusCode(200)
                .extract()
                .as(List.class);
        assertEquals(1, list.size());
    }
}
expected: <1> but was: <2>
Expected :1
Actual   :2
<Click to see difference>

org.opentest4j.AssertionFailedError: expected: <1> but was: <2>
	at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at app//org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
	at app//org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
	at app//org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:528)
	at app//au.com.controller.BookController.secondInsert(BookController.java:73)

Environment Information

micronaut 3.9.4
micronaut-test-rest-assured:3.9.4

Example Application

No response

Version

3.9.4

@caimberRoberto
Copy link

I have the same issue in version 4.3.6
After some research I have reached two conclusions:

  • that something is wrong with the transaction status propagation mechanism (TransactionStatus object), so that the contoller does not know that it has to rollback.
  • or that the rollback process only works for the changes that are made directly in the test, not for those that are made by calling the endpoints of the controllers.

@graemerocher
Copy link
Contributor

when you are making real HTTP client requests these are sent to the running server which runs a separate transaction in a completely different thread from the test.

Currently it is not possible to use the same transaction in the test method and the server running a completely different thread in the controller. For this case you need to implement manual rollback.

@sdelamo
Copy link
Contributor

sdelamo commented May 9, 2024

to implement manual rollback you typically, annotate with the test with @MicronautTest(transactional = false) and then you make sure to delete any side effects your test creates. For example:

package example.micronaut;

import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest(transactional = false)
class FruitControllerTest {

    @Test
    void itIsPossibleToNavigateWithCursoredPage(@Client("/") HttpClient httpClient,
                                                FruitRepository repository) {
        List<Fruit> data = List.of(
                new Fruit(null, "apple"),
                new Fruit(null, "banana"),
                new Fruit(null, "cherry"),
                new Fruit(null, "date"),
                new Fruit(null, "elderberry"),
                new Fruit(null, "fig"),
                new Fruit(null, "grape"),
                new Fruit(null, "honeydew"),
                new Fruit(null, "kiwi"),
                new Fruit(null, "lemon")
        );
        repository.saveAll(data);
        int numberOfFruits = data.size();
        assertEquals(numberOfFruits, repository.count());

        BlockingHttpClient client = httpClient.toBlocking();
        HttpResponse<List<Fruit>> response = assertDoesNotThrow(() ->
                client.exchange(HttpRequest.GET("/fruits"), (Argument.listOf(Fruit.class))));
        assertEquals(HttpStatus.OK, response.getStatus());
        List<Fruit> fruits = response.body();
        assertEquals(numberOfFruits, fruits.size());
        // manual rollback
        repository.deleteAll();
    }
}

@BagasAjah
Copy link
Author

when you are making real HTTP client requests these are sent to the running server which runs a separate transaction in a completely different thread from the test.

Currently it is not possible to use the same transaction in the test method and the server running a completely different thread in the controller. For this case you need to implement manual rollback.

Thanks for the answer.
Yeah, at the end of the day, I did a manual rollback before each test scenario to ensure I had clean data for each test scenario.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants