{"id":16247,"date":"2025-06-19T00:00:00","date_gmt":"2025-06-19T07:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ise\/?p=16247"},"modified":"2025-06-27T01:59:31","modified_gmt":"2025-06-27T08:59:31","slug":"pact-contract-testing-because-not-everything-needs-full-integration-tests","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/pact-contract-testing-because-not-everything-needs-full-integration-tests\/","title":{"rendered":"PACT Contract Testing &#8211; Because Not Everything Needs Full Integration Tests"},"content":{"rendered":"<p>At Microsoft Industry Solutions Engineering, we are big fans of integration tests. They are the best way to test that\nyour system works as a whole, but sometimes, integration tests are overkill or too complex to set up that the return on\ninvestment is just not worth it. That&#8217;s the situation one of our teams has recently found themselves in.<\/p>\n<p>The project in question was a proof of concept, a natural language search engine for a large manufacturing company. The\ndata was complex, inconsistent, and spread across multiple downstream systems managed by different teams. These systems\nvaried widely in engineering maturity. Some had integration test environments, while others did not. Since this was a\nproof of concept, the team decided not to invest time in setting up integration tests for every downstream system.\nHowever, they still needed a way to test the interactions between these systems effectively.<\/p>\n<p>So what do you do then? You could write unit tests, but they only test the applications in isolation. That&#8217;s where\ncontract testing comes in.<\/p>\n<p>Looking at the table below, you can see that contract tests are a good middle ground between unit and integration tests.\nThey are fast to run, easy to set up, and provide a good level of coverage.<\/p>\n<table>\n<thead>\n<tr>\n<th>Test Type<\/th>\n<th>Complexity<\/th>\n<th>Speed<\/th>\n<th>Coverage<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Contract<\/td>\n<td>Low<\/td>\n<td>Fast<\/td>\n<td>Medium<\/td>\n<\/tr>\n<tr>\n<td>Unit<\/td>\n<td>Low<\/td>\n<td>Fast<\/td>\n<td>Low<\/td>\n<\/tr>\n<tr>\n<td>Integration<\/td>\n<td>High<\/td>\n<td>Slow<\/td>\n<td>High<\/td>\n<\/tr>\n<tr>\n<td>Regression<\/td>\n<td>Medium<\/td>\n<td>Medium<\/td>\n<td>Medium<\/td>\n<\/tr>\n<tr>\n<td>Smoke<\/td>\n<td>Low<\/td>\n<td>Fast<\/td>\n<td>Very Low<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In this post, we will explain what contract testing is, why it is useful, and how to use PACT to write contract tests for\nyour applications.<\/p>\n<h2>What is contract testing?<\/h2>\n<p>Contract testing is a way to test the interactions between two applications. It allows you to define a contract between\nthe two applications, which specifies how they should interact. This contract can then be used to test both systems in\nisolation, without the need for a full integration test.<\/p>\n<p>With contract testing, you achieve the simplicity and speed of unit tests while significantly improving coverage,\nbringing it closer to the level of integration tests, but without the associated complexity or overhead.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2025\/06\/venn-comparison.png\" alt=\"Venn diagram showing contract testing in the middle of unit and integration tests\" \/><\/p>\n<h2>What is PACT?<\/h2>\n<p><a href=\"https:\/\/docs.pact.io\/\">PACT<\/a> is a contract testing tool that allows you to define a contract between two applications.\nIt is a consumer-driven contract testing tool, which means that the consumer of the API defines the contract. The\nprovider of the API then uses this contract to test that it meets the requirements of the consumer. This allows you to\ntest the interactions between the two applications without the need for a full integration test.<\/p>\n<p>PACT is available in multiple languages, including Java, JavaScript, Ruby, and Go. This means that you can use PACT to\ntest applications written in different languages, which is especially useful in microservices architectures where\ndifferent services may be written in different languages. However, we have found that the PACT libraries for each language\ndo not have feature parity. For example the Java version of PACT is much more mature than the Python version. So, ensure\nyou verify the level of support before committing to using PACT in a less-supported language. You can find the list of\nsupported languages <a href=\"https:\/\/docs.pact.io\/getting_started\/specification#client-language-support\">here<\/a>.<\/p>\n<p>PACT is not just limited to REST APIs. It also supports testing interactions with message brokers, such as Kafka.<\/p>\n<h2>How does PACT work?<\/h2>\n<p>PACT works by defining a contract between the consumer and provider of an API. The consumer defines the contract by\nwriting a unit test that specifies the expected interactions with the API. This test is then used to generate a contract\nfile, which is a JSON file that contains the details of the contract. The provider then uses this contract file to test\nthat it meets the requirements of the consumer.<\/p>\n<h3>The contract file<\/h3>\n<p>You can see an example of a contract file below:<\/p>\n<pre><code class=\"language-json\">{\n  \"consumer\": {\n    \"name\": \"OrderConsumer\"\n  },\n  \"provider\": {\n    \"name\": \"OrderProvider\"\n  },\n  \"interactions\": [\n    {\n      \"description\": \"a request for a order\",\n      \"providerStates\": [\n        {\n          \"name\": \"order with ID 88 exists\"\n        }\n      ],\n      \"request\": {\n        \"method\": \"GET\",\n        \"path\": \"\/order\/88\",\n        \"headers\": {\n          \"Accept\": \"application\/json\"\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application\/json\"\n        },\n        \"body\": {\n          \"id\": 1,\n          \"items\": [\n            \"milkshake\",\n            \"burger\"\n          ],\n          \"date\": \"2025-03-26T12:00:00Z\"\n        }\n      },\n      \"matchingRules\": {\n        \"body\": {\n          \"$.id\": {\n            \"combine\": \"AND\",\n            \"matchers\": [\n              {\n                \"match\": \"type\"\n              }\n            ]\n          },\n          \"$.items\": {\n            \"combine\": \"AND\",\n            \"matchers\": [\n              {\n                \"match\": \"type\"\n              }\n            ]\n          },\n          \"$.date\": {\n            \"combine\": \"AND\",\n            \"matchers\": [\n              {\n                \"format\": \"yyyy-MM-dd'T'HH:mm:ss\",\n                \"match\": \"date\"\n              }\n            ]\n          }\n        }\n      }\n    }\n  ]\n}<\/code><\/pre>\n<p>The contract file contains the details of the interactions between the consumer and provider. It specifies the request\nand response details, including the HTTP method, path, headers, and body. It also specifies the matching rules for the\nrequest and response bodies, which allows you to specify things such as date patterns. The matching rules specify how\nthe request and response bodies should be matched, which allows you to test that the provider meets the requirements of\nthe consumer. It also specifies the provider state, which can be used to set up the state of the provider before the\ninteraction is verified, for example creating a mock order with ID 88 in your database or in-memory store. This will\nensure that when the consumer sends a request for order ID 88, it will receive a valid response.<\/p>\n<h3>The consumer side<\/h3>\n<p>The consumer side of PACT is where you define the contract. The first step is to create a test class for your client\nthat will call the provider system:<\/p>\n<pre><code class=\"language-java\">package com.example.pactdemo.client;\n\nimport au.com.dius.pact.consumer.MockServer;\nimport au.com.dius.pact.consumer.dsl.LambdaDslJsonBody;\nimport au.com.dius.pact.consumer.dsl.PactDslWithProvider;\nimport au.com.dius.pact.consumer.junit5.PactConsumerTest;\nimport au.com.dius.pact.consumer.junit5.PactTestFor;\nimport au.com.dius.pact.core.model.V4Pact;\nimport au.com.dius.pact.core.model.annotations.Pact;\nimport au.com.dius.pact.core.model.annotations.PactDirectory;\n\n\/\/ full import list omitted\n\n@PactConsumerTest\n@PactDirectory(\"..\/pacts\")\n@PactTestFor(providerName = \"PactDemoProvider\")\npublic class OrderClientTest {<\/code><\/pre>\n<p>You then define the contract using the <code>@Pact<\/code> annotation. The <code>PactDslWithProvider<\/code> class is used to define and build\nthe request and response details, including the HTTP method, path, headers, and body. The <code>LambdaDslJsonBody<\/code> class is\nused to define the matching rules for the request and response bodies:<\/p>\n<pre><code class=\"language-java\">    @Pact(provider = \"PactDemoProvider\", consumer = \"PactDemoConsumer\")\n    public V4Pact orderPact(PactDslWithProvider builder) {\n        LambdaDslJsonBody expectedResponseBody = newJsonBody((expectedOrder) -&gt; {\n              expectedOrder.stringType(\"id\", \"88\");\n              expectedOrder.object(\"user\", (user) -&gt; {\n                    user.stringType(\"id\", \"881985\");\n                    user.stringType(\"firstName\", \"Sam\");\n                    user.stringType(\"lastName\", \"Jones\");\n                    user.stringType(\"email\", \"sam.jones@hungry.com\");\n              });\n              expectedOrder.array(\"items\", (item) -&gt; {\n                    item.object((itemObj) -&gt; {\n                          itemObj.stringType(\"id\", \"1\");\n                          itemObj.stringType(\"name\", \"Milkshake\");\n                          itemObj.stringType(\"description\", \"A delicious milkshake made with real ice cream and topped with whipped cream.\");\n                          itemObj.numberType(\"price\", 5.99);\n                    });\n                    item.object((itemObj) -&gt; {\n                          itemObj.stringType(\"id\", \"2\");\n                          itemObj.stringType(\"name\", \"Burger\");\n                          itemObj.stringType(\"description\", \"A juicy beef burger with lettuce, tomato, and cheese.\");\n                          itemObj.numberType(\"price\", 12.99);\n                    });\n              });\n              expectedOrder.numberType(\"totalPrice\", 18.98);\n              expectedOrder.date(\"orderDate\", \"yyyy-MM-dd'T'HH:mm:ss\", Date.from(LocalDateTime.parse(\"2015-10-21T10:30:00\").toInstant(UTC)));\n        });\n\n        return builder\n                    .given(\"order with ID 88 exists\")\n                    .uponReceiving(\"a request to get an order by id\")\n                    .path(\"\/order\/88\")\n                    .method(\"GET\")\n                    .headers(\"Accept\", \"application\/json\")\n                    .willRespondWith()\n                    .status(200)\n                    .headers(Map.of(\"Content-Type\", \"application\/json\"))\n                    .body(expectedResponseBody.build())\n                    .toPact(V4Pact.class);\n    }<\/code><\/pre>\n<p>Finally, you can use the <code>@PactTestFor<\/code> annotation to specify the provider system that you are testing against:<\/p>\n<pre><code class=\"language-java\">    @Test\n    @PactTestFor(pactMethod = \"orderPact\")\n    void testGetOrder(MockServer mockServer) throws Exception {\n        OrderClient orderClient = new OrderClient(mockServer.getUrl());\n\n        Order order = orderClient.getOrder(\"88\");\n\n        assertNotNull(order);\n    }<\/code><\/pre>\n<p>The <code>@PactTestFor<\/code> annotation specifies the provider system that you are testing against, <code>orderPact<\/code> in this example.\nThis needs to match the name of the method that defines the contract.<\/p>\n<p>Pact starts a mock server that simulates the provider system. The <code>MockServer<\/code> object is used to get the URL of the mock\nserver, which is then passed to the <code>OrderClient<\/code> to test against.<\/p>\n<h3>The provider side<\/h3>\n<p>The provider side of PACT is where you verify that the provider system meets the requirements of the consumer. The\nfirst step is to create a test class for your provider system that will verify the contract. In our example, the\nprovider system is a Spring Boot application, so we can use the <code>@SpringBootTest<\/code> annotation to start the application\nand run the tests against it:<\/p>\n<pre><code class=\"language-java\">package com.example.pactdemo;\n\nimport au.com.dius.pact.provider.junit5.HttpTestTarget;\nimport au.com.dius.pact.provider.junit5.PactVerificationContext;\nimport au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;\nimport au.com.dius.pact.provider.junitsupport.Provider;\nimport au.com.dius.pact.provider.junitsupport.State;\nimport au.com.dius.pact.provider.junitsupport.loader.PactFolder;\n\n\/\/ full import list omitted\n\n@Provider(\"PactDemoProvider\")\n@PactFolder(\"..\/pacts\")\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\nclass PactDemoApplicationTests {<\/code><\/pre>\n<p>We then need to setup the test context and specify the target URL of the provider system. As we&#8217;re starting up the\nSpring Boot application, we can use the <code>@LocalServerPort<\/code> annotation to get the port that the application is running on\nand then set the target URL in the <code>setUp<\/code> method. However, you can also test against a real deployed service if you\nwish to be setting the target URL to the real service:<\/p>\n<pre><code class=\"language-java\">    @LocalServerPort\n    int port;\n\n    @BeforeEach\n    void setUp(PactVerificationContext context) {\n        context.setTarget(new HttpTestTarget(\"localhost\", port, \"\/\"));\n    }\n\n    @TestTemplate\n    @ExtendWith(PactVerificationInvocationContextProvider.class)\n    void verifyPact(PactVerificationContext context) {\n        context.verifyInteraction();\n    }<\/code><\/pre>\n<p>The <code>@TestTemplate<\/code> verifies the interactions defined in the contract file. You can also use this to add other things\nsuch as authentication or other headers that are required by the provider system.<\/p>\n<p>We now need to define our tests:<\/p>\n<pre><code class=\"language-java\">    @State({\n        \"order with ID 88 exists\",\n        \"order with ID 999 does not exist\"\n    })\n    void testPactWhenOrderWithIdRequestReceived() {\n        \/\/ This method is used to set up the state of the provider before the interaction is verified.\n        \/\/ You can use this method to create any necessary data or perform any actions needed to set up the state.\n        \/\/ For example, you could create a mock order with ID 88 in your database or in-memory store.\n        \/\/ This will ensure that when the consumer sends a request for order ID 88, it will receive a valid response.\n        \/\/ Similarly, you can set up the state for order ID 999 to return a 404 response.\n    }<\/code><\/pre>\n<p>The <code>@State<\/code> annotation specifies the provider state that is required for the interaction to be verified. The rest of\nthe test is empty, as the <code>@TestTemplate<\/code> will take care of verifying the interaction. But if you need to set up any\ndata or perform any actions needed to set up the state, you can do that here.<\/p>\n<h3>Where to store the contract files<\/h3>\n<p>In the examples above, we have used the <code>@PactDirectory<\/code> annotation to specify the location of the contract files. These\ncan then just be committed to git. However, unless you&#8217;re using a monorepo, you will need to store the contract files in\na shared location that is accessible to both the consumer and provider systems. This could be a dedicated contract file\nrepository, where the consumers will need to manually copy the contract files and the providers will need to test\nagainst a pre-deployed system.<\/p>\n<p>Alternatively, you can use the <a href=\"https:\/\/docs.pact.io\/pact_broker\">PACT broker<\/a> to store the contract files. The PACT\nbroker is a service that stores the contracts. A <a href=\"https:\/\/docs.pact.io\/pact_broker\/docker_images\">Docker image<\/a> is\navailable for the PACT broker, as well as <a href=\"https:\/\/docs.pact.io\/pact_broker\/kubernetes\/readme\">Helm charts<\/a> for\ndeployment to Kubernetes.<\/p>\n<h2>Conclusion<\/h2>\n<p>So to summarise, PACT is a fantastic contract testing tool that takes the pain out of hand crafting contracts. If\nintegration tests are overkill or too complex to set up, as was the case for our team working on a proof of concept,\nthen contract testing using PACT is a great alternative.<\/p>\n<p><em>The feature image was generated using Bing Image Creator. <a href=\"https:\/\/www.bing.com\/new\/termsofuse?FORM=GENTOS\">Terms<\/a> can be found here.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>PACT is a contract testing tool that allows you to define a contract between two applications, enabling<\/p>\n","protected":false},"author":186544,"featured_media":16261,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,17,3451],"tags":[3605,3510,3400,219,3604,3457,3346],"class_list":["post-16247","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","category-frameworks","category-ise","tag-contract-testing","tag-integration-testing","tag-ise","tag-java","tag-pact","tag-spring-boot","tag-testing"],"acf":[],"blog_post_summary":"<p>PACT is a contract testing tool that allows you to define a contract between two applications, enabling<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/16247","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/186544"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=16247"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/16247\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/16261"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=16247"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=16247"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=16247"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}