I've been experimenting with Contract Testing using Spring Cloud Contract (SCC) and am now trying to use Pact in combination with SCC to serve as an intermediate step before going pure Pact.
On my consumer project I've specified a simple contract:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AccountServicePactTest {
@Autowired
TransactionService transactionService;
@Rule
public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("account-service", "localhost", 8081, this);
@Pact(consumer = "transaction-service")
public RequestResponsePact createPact(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json;charset=UTF-8");
return builder
.given("An account with UUID 8ea0f76b-b7a6-49eb-b25c-073b664d2de3 exists")
.uponReceiving("Request for an account by UUID")
.path("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3")
.method("GET")
.willRespondWith()
.headers(headers)
.status(200)
.body("{\n" +
" \"accountUUID\": \"8ea0f76b-b7a6-49eb-b25c-073b664d2de3\",\n" +
" \"customerId\": 1,\n" +
" \"balance\": 0.00,\n" +
"}")
.toPact();
}
@Test
@PactVerification
public void runTest() {
AccountRetrievalRequest request = new AccountRetrievalRequest(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
AccountDTO accountDTO = transactionService.retrieveAccount(request);
assertThat(accountDTO.getAccountUUID()).isEqualTo(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
}
}
(I know that hardcoding the UUID, customerId and balance makes the test brittle, but this is just a simple test)
Using the pact-jvm-provider-maven_2.12 plugin (provider plugin on consumer side, confusing I know) the Pact file gets pushed to a Pact Broker:
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.22</version>
<configuration>
<pactBrokerUrl>http://localhost:8888</pactBrokerUrl>
<pactBrokerUsername></pactBrokerUsername>
<pactBrokerPassword></pactBrokerPassword>
<trimSnapshot>true</trimSnapshot>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>publish</goal>
</goals>
</execution>
</executions>
</plugin>
So far so good.
On the provider project I run into issues trying to verify the the contract. Again, I'm using SCC and Pact together. pom.xml plugins snippet:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<contractsRepositoryUrl>pact://http://localhost:8888</contractsRepositoryUrl>
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>+</version>
</contractDependency>
<contractsMode>REMOTE</contractsMode>
<packageWithBaseClasses>com.abnamro.internship.bank.accountservice.pacts</packageWithBaseClasses>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring-cloud-contract.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.11</version>
<configuration>
<serviceProviders>
<serviceProvider>
<name>account-service</name>
<pactBrokerUrl>http://localhost:8888/</pactBrokerUrl>
</serviceProvider>
</serviceProviders>
</configuration>
</plugin>
SCC Verifier required Base Class:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private AccountRepository accountRepository;
@Before
public void setup() {
Account account = new Account(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"), Integer.toUnsignedLong(1),
0.00d);
accountRepository.save(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
System.out.println(account.getAccountUUID());
}
@After
public void teardown() {}
}
SCC generated test:
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ContractsTest extends Account_serviceContractsBase {
@Test
public void validate_0_account_service_pact() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['accountUUID']").isEqualTo("8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
assertThatJson(parsedJson).field("['customerId']").isEqualTo(1);
assertThatJson(parsedJson).field("['balance']").isEqualTo(0.0);
}
}
Running the test gives the following exception:
java.lang.IllegalArgumentException: json string can not be null or empty
I can't figure out why the string is either empty or null. Running the application and using Postman to test the endpoint works just fine.
This is part of the Controller:
@RestController
@RequestMapping("/api")
public class AccountController {
...
@GetMapping("/accounts/{accountUUID}")
public ResponseEntity<Account> retrieveAccount(@PathVariable("accountUUID") UUID accountUUID) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return new ResponseEntity<>(accountService.retrieveAccount(accountUUID),
httpHeaders, HttpStatus.OK);
}
I'm missing something but I can't find what. Thoughts?
EDIT: The (generated) test passed when just using SCC.
Turned out my Base Class wasn't setup properly. Working Base Class which simply mocks the called service method and returns a test account:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
@Autowired
private WebApplicationContext webApplicationContext;
@MockBean
private AccountService accountService;
@Before
public void setup() {
Account account = new Account();
account.setAccountUUID(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
account.setCustomerId(1L);
account.setBalance(0.00d);
when(accountService.retrieveAccount(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"))).thenReturn(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
@After
public void teardown() {}
}