TNG / JGiven

Behavior-Driven Development in plain Java
http://jgiven.org
Apache License 2.0
436 stars 99 forks source link

Has anybody tried to combine Pact-JVM tests with JGiven tests ? #1676

Closed bodote closed 1 month ago

bodote commented 1 month ago

Over the past few months, we have successfully utilized JGiven for new tests in our Spring Boot project. As this Spring Boot application communicates with other REST services, we have been tasked with conducting Consumer Driven Contract tests using Pact-JVM.

We are now contemplating whether it is advisable or feasible to integrate Pact-JVM tests with JGiven tests. Specifically, we are considering writing JGiven tests as @SpringBootTest or @WebMvcTest that also generate Pact contracts.

l-1squared commented 1 month ago

Hi, first off, sorry for the late reply. Unfortunately I am not exactly familiar with Pact, so my advice is based off a sample test from Chat-GPT:

import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.annotations.Pact;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(PactConsumerTestExt.class)
@SpringBootTest
public class UserServicePactTest {

    @Autowired
    private RestTemplateBuilder restTemplateBuilder;

    @Pact(consumer = "UserServiceConsumer", provider = "UserServiceProvider")
    public RequestResponsePact createPact(PactDslWithProvider builder) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");

        return builder
                .given("User with ID 1 exists")
                .uponReceiving("A request to get user with ID 1")
                .path("/users/1")
                .method("GET")
                .willRespondWith()
                .status(200)
                .headers(headers)
                .body("{\"id\": 1, \"name\": \"John Doe\"}")

                .given("A request to create a new user")
                .uponReceiving("A request to create a user")
                .path("/users")
                .method("POST")
                .headers(headers)
                .body("{\"name\": \"Jane Doe\"}")
                .willRespondWith()
                .status(201)
                .headers(headers)
                .body("{\"id\": 2, \"name\": \"Jane Doe\"}")
                .toPact();
    }

    @PactTestFor(pactMethod = "createPact")
    public void testGetUser(MockServer mockServer) {
        RestTemplate restTemplate = restTemplateBuilder.rootUri(mockServer.getUrl()).build();
        ResponseEntity<String> response = restTemplate.getForEntity("/users/1", String.class);

        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).contains("John Doe");
    }

    @PactTestFor(pactMethod = "createPact")
    public void testPostUser(MockServer mockServer) {
        RestTemplate restTemplate = restTemplateBuilder.rootUri(mockServer.getUrl()).build();
        String newUser = "{\"name\": \"Jane Doe\"}";
        ResponseEntity<String> response = restTemplate.postForEntity("/users", newUser, String.class);

        assertThat(response.getStatusCodeValue()).isEqualTo(201);
        assertThat(response.getBody()).contains("Jane Doe");
    }
}

Given that the code of pack already looks highly abstracted, I guess you aim at getting a nice report out of the test. From the test class, it seems that the @Pact method is most akin to a "Given" stage while the 2 blocks in the @PactTest Method would be the 'when' and 'then' stages. Wrapping the latter two into Stages would seem fairly straightforward. However wrapping the pact itsself would seem a bit of a hassle... For one, the pact statement her contains 2 pacts in one method. This will probably be impossible for JGiven to keep apart. The other thing is that stages are essential for Given, so you'd somehow have to make the Pact Builder methods stage methods and then make sure that the JGiven scenario is started before Pact invokes the @Pact method.

I would be interested though, if you managed to get these two working together.

best, l-1squared

m-2squared commented 1 month ago

If there are no further questions, I would close thiss issue @bodote