springspring-data-jpaspockwebtestclient

Repository inserts don't work in SpringBootTest with WebTestClient


I am converting a spring boot application to use webflux by making the endpoints reactive one at a time. Most of the (Spock) tests worked fine, but the ones where I autowire the JPA repository in the test to insert data do not. The repository is always empty when the resource (that we're testing) reads from it. I have tens of tests like this with different repositories and they all have same issue.

Here is an example of a test (the resource we're testing just does findById and returns the example from the repository):

@SpringBootTest
@AutoConfigureWebTestClient(timeout = "60000")
@Transactional
class PaymentControllerIntegrationTest extends Specification {

    @Autowired
    WebTestClient client

    @Autowired
    PaymentRepository repo

    def "GET /example/{id} returns correct example"() {
        given:
        def needle = new Example(id: 1L)
        def haystack = repo.saveAll([needle, new Example(id: 2L), new Example(id: 3L)])

        when:
        def response = client.get().uri(EXAMPLE_URL, [id: needle.id.toString()]).exchange()

        then:
        response.expectStatus().isOk()
        response.returnResult(ExampleResponse.class).getResponseBody().blockLast().id == needle.id
    }

When I put a breakpoint in the controller and do findAll() the repository is always empty.

My best guess currently is that the test is setup incorrectly (application context?) so the repository in the test is not the same as the repository in the application.


Solution

  • When using the TestWebClient (or the TestRestTemplate) you are actually issuing a real HTTP request to your started server. This request is handled in a different thread and as such uses a new transaction.

    Your test is transactional as well but the data hasn't been committed, another transaction can only read committed data (or you need to set the isolation level to READ_UNCOMMITTED but that is probably not something you should or want to do).

    When using MockMvc you are replacing the actual container with a mocked instance and it uses a MockHttpServletRequest etc. and executes in the same thread (and thus reuse the same transaction and can see the data).

    To solve, make your test not transactional and cleanup the data afterwards. This will however impact the performance of your tests (as committing and then deleting is slower as rolling back a transaction after the test).