Search code examples
spring-bootjdbctemplatemockserver

Trying to update embedded database when mocked http response is returned using Wiremock of MockServer


Working in a Spring Boot context, I am testing a service that queries a db, then makes a remote call that updates the db, then requeries the db to get the updated data.

I am trying to use Wiremock or MockServer to mock the remote call, but can't figure out how to update the embedded database as part of the mock server response generation process.

For example, using MockServer, I tried creating a callback class that had an autowired JdbcTemplate instance, but in the context of the callback that JdbcTemplate variable is null.

    public class ApprovalHappyPathCallback implements ExpectationResponseCallback {
    JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource ds) {
        jdbcTemplate = new JdbcTemplate(ds);
    }

    public static final HttpResponse httpResponse = response()
            .withStatusCode(HttpStatusCode.ACCEPTED_202.code())
            .withHeaders(
                    header("x-callback", "test_callback_header"),
                    header("Content-Length", "a_callback_response".getBytes(UTF_8).length),
                    header("Connection", "keep-alive")
            )
            .withBody("a_callback_response");

    @Override
    public HttpResponse handle(HttpRequest httpRequest) {
        if (httpRequest.getMethod().equals("GET")) {
            jdbcTemplate.execute("update communications set status = 'APPROVED_SCHEDULED' where id = 153511");
            return httpResponse;
        } else {
            return notFoundResponse();
        }
    }
}

The call back executes, but the jdbcTemplate statement does not work.

The callback is referenced like this in the test:

mockServer.when(request().withMethod("GET"))
        .withBody("Approved")
        // );
        .respond(
            callback()
                .withCallbackClass(ApprovalHappyPathCallback.class)
        );

The service method that makes the remote call is essentially:

public CommunicationEntity approveCommunication(Long communicationId) {
    String approvalToken = commRepo.approvalTokenById(communicationId);
        if (approvalToken == null) {
            approvalToken = UUID.randomUUID().toString();
            communicationEntity.setApprovalToken(approvalToken);
            commRepo.save(communicationEntity);
        }
    String approvalResponse = remoteCommunicationApprover.approveCommunication(communicationId, approvalToken);
    CommunicationEntity communicationEntity = getCommunicationById(communicationId);

        if (communicationEntity.getStatus() != CommunicationStatus.Approved_Scheduled) {
            throw new BadRequestException(
                    "Approval request for communication " + communicationId + " and token " + approvalToken
                            + " failed with remote response: " + approvalResponse,
                    ErrorCodes.COMMUNICATION_SVC_REMOTE_APPROVAL_REQUEST_FAILED);
        }
        return communicationEntity;



Solution

  • There were two issues causing problems: making sure the jdbcTemplate used in the callback method was configured with the correct DataSource, and making sure that the data in the embedded in memory DB was accessible from the MockServer response generation thread.

    I solved the first problem by using a lambda or closure for the MockServer callback in which I use the JdbcTemplate instance created in the test class with the autowired DataSource (though solutions exist for the callback class approach as well).

    The second problem was the result of the fact that the test method was within a transaction and so inserts to the DB made at the beginning of the test were not committed when the MockServer thread (note that the MockServer response generation happens in a different thread than the main thread where the test method is running) was executing the callback. Thus those inserts were not accessible to the callback.

    The solution was to annotate the test method with @Transactional(propagation = Propagation.NOT_SUPPORTED) See h2 database access to test data from separate threads