{"id":13817,"date":"2020-10-27T05:39:49","date_gmt":"2020-10-27T09:39:49","guid":{"rendered":"https:\/\/qxf2.com\/blog\/?p=13817"},"modified":"2020-10-27T05:39:49","modified_gmt":"2020-10-27T09:39:49","slug":"writing-contract-test-using-pact","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/writing-contract-test-using-pact\/","title":{"rendered":"Writing Contract test for API endpoint using Pact"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">This post shows you a short example of how to use the Python module <a href=\"https:\/\/pypi.org\/project\/pact-python\/\">Pact<\/a> to write Consumer Contract tests against Provider Contract. We will use Trello API endpoints for writing sample test. Note that this blog will only cover one sample test. Testers can write many more for a different response such as unauthorized access, server not found.<br \/>\n<\/span><\/p>\n<hr>\n<h3>What is Contract testing?<\/h3>\n<p><code>Contract testing<\/code> is a testing technique used for testing endpoints, for applications which are isolated. Messages sent or received confirms that they are adhering to a written <code>Pact<\/code> or <code>Contract<\/code>. This helps the tester to confirm all calls made to the Mock Provider, returns the same response as actual service. This <a href=\"https:\/\/docs.pact.io\/#what-is-contract-testing\">reference link<\/a> will give more information about <code>Contract testing<\/code>.<\/p>\n<hr>\n<h3>Background<\/h3>\n<p>Python package of Pact helps in writing consumer-driven python tests. As shown in the below diagram, while running Pact Test, <code>Consumer<\/code> does not contact actual <code>Provider<\/code>, but it will contact Mock Provider. The mock provider will validate the defined contract and send the appropriate response. The advantage over here is you don&#8217;t have to spin up actual <code>Provider<\/code> setup.<\/p>\n<hr>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2020\/10\/Pact-Test-Flow-lucidchart-2.png\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2020\/10\/Pact-Test-Flow-lucidchart-2.png\" alt=\"\" \/><\/a><\/p>\n<hr>\n<h3>API Endpoint under Test<\/h3>\n<p>We will be using <code>\/1\/boards\/{id}<\/code> endpoint for GET request of Trello API&#8217;s. This request will be used to fetch a single Trello board based on the ID. More information can be found at <a href=\"https:\/\/developer.atlassian.com\/cloud\/trello\/rest\/\">Trello API Documentation<\/a><\/p>\n<hr>\n<h3>Using pact-python<\/h3>\n<p>The Python module pact makes it really easy for us to define pact between REST API endpoint Provider and Consumer of those endpoints. The next steps will help to write the first test:<\/p>\n<p>1.Define the Consumer and Provider objects that describe API endpoint and expected payload<br \/>\n2.Define the setup criteria for the Provider<br \/>\n3.Define the Consumer request using Pact<br \/>\n4.Define how the provider is expected to respond using Pact<br \/>\n5.Assert the response to validate the contract<\/p>\n<hr>\n<h5>1. Define the Consumer and Provider objects that describe API endpoint and expected payload<\/h5>\n<p>Create a new file called <code>test_trello_get_board.py<\/code> and add the following lines. Note that you will require to import <a href=\"https:\/\/docs.python.org\/3\/library\/unittest.html\">Unittest<\/a>, <a href=\"https:\/\/docs.python.org\/3\/library\/atexit.html\">atexit<\/a> and <a href=\"https:\/\/docs.pytest.org\/en\/latest\/\">Pytest<\/a> to run the test.<\/p>\n<pre lang=\"python\">\r\n\"contract test for Trello board\"\r\nimport os\r\nimport json\r\nimport atexit\r\nimport unittest\r\nimport pytest\r\nimport requests\r\nfrom pact import Consumer, Provider\r\n\r\n# Setting up pact\r\npact = Consumer('Consumer').has_pact_with(Provider('Provider'))\r\npact.start_service()\r\natexit.register(pact.stop_service)\r\n\r\n#setting up path for pact file\r\nCURR_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\r\nPACT_DIR = os.path.join(CURR_FILE_PATH, '')\r\nPACT_FILE = os.path.join(PACT_DIR, 'pact.json')\r\n\r\n# Defining Class\r\nclass GetBoard(unittest.TestCase):\r\n  def test_get_board(self):\r\n   # Defining test method\r\n     with open(os.path.join(PACT_DIR, PACT_FILE), 'rb') as pact_file:\r\n        pact_file_json = json.load(pact_file)\r\n        expected = pact_file_json\r\n      \r\n<\/pre>\n<h5>2. Define the setup criteria for the Provider<\/h5>\n<p>Using <code>.given<\/code>, setup criteria for the Provider will be defined as shown below.<\/p>\n<pre lang=\"python\">\r\n(pact\r\n  .given('Response Payload will be received as expected')\r\n  .upon_receiving('a request for get trello board')     \r\n<\/pre>\n<h5>3. Define the Consumer request using Pact<\/h5>\n<p>Using <code>.with_request<\/code>, you can define the request type and API endpoint to which the request needs to be made.<\/p>\n<pre lang=\"python\">\r\n(pact\r\n  .given('Request for get trello board')\r\n  .upon_receiving('a request for get trello board')\r\n  .with_request('GET','\/1\/boards\/1000')\r\n\r\n<\/pre>\n<h5>4. Define how the Provider is expected to response using Pact<\/h5>\n<p>You can define the Provider response using <code>.will_respond_with<\/code> which will include status code and expected contract payload.<\/p>\n<pre lang=\"python\">\r\n(pact\r\n  .given('Request for get trello board')\r\n  .upon_receiving('a request for get trello board')\r\n  .with_request('GET','\/1\/boards\/1000')\r\n  .will_respond_with(200, body=expected))\r\n\r\n<\/pre>\n<h5>5. Assert the response with expected contract<\/h5>\n<p>The below steps will help you to assert the response with a defined expected contract.<\/p>\n<pre lang=\"python\">\r\nwith pact:\r\n    result = requests.get(pact.uri + '\/1\/boards\/1000')\r\n\r\n    self.assertEqual(result.json(), expected)\r\n    pact.verify()\r\n\r\n<\/pre>\n<hr>\n<h3>Putting it all together<\/h3>\n<p>Here is how our test looks:<\/p>\n<pre lang=\"python\">\r\n\"contract test for trello board\"\r\nimport os\r\nimport json\r\nimport logging\r\nimport atexit\r\nimport unittest\r\nimport pytest\r\nimport requests\r\nfrom pact import Consumer, Provider, Format\r\n\r\n# Declaring logger\r\nlog = logging.getLogger(__name__)\r\nlogging.basicConfig(level=logging.INFO)\r\nprint(Format().__dict__)\r\n\r\n# Setting up pact\r\npact = Consumer('Consumer').has_pact_with(Provider('Provider'))\r\npact.start_service()\r\natexit.register(pact.stop_service)\r\n\r\n#setting up path for pact file\r\nCURR_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\r\nPACT_DIR = os.path.join(CURR_FILE_PATH, '')\r\nPACT_FILE = os.path.join(PACT_DIR, 'pact.json')\r\n\r\n# Defining Class\r\nclass GetBoard(unittest.TestCase):\r\n  def test_get_board(self):\r\n     \"\"\"\r\n     Defining test method\r\n     \"\"\"\r\n     with open(os.path.join(PACT_DIR, PACT_FILE), 'rb') as pact_file:\r\n        pact_file_json = json.load(pact_file)\r\n        expected = pact_file_json\r\n\r\n        (pact\r\n         .given('Request to send message')\r\n         .upon_receiving('a request for response for send message')\r\n         .with_request('GET','\/1\/boards\/1000')\r\n         .will_respond_with(200, body=expected))\r\n\r\n        with pact:\r\n           result = requests.get(pact.uri + '\/1\/boards\/1000')\r\n\r\n        self.assertEqual(result.json(), expected)\r\n        pact.verify()\r\n\r\n<\/pre>\n<p>A sample pact.json file would look like below.<\/p>\n<pre lang=\"json\">\r\n# pact.json\r\n{\r\n    \"id\":\"1000\",\r\n \"desc\":\"Track changes to Trello's Platform on this board.\",\r\n \"descData\":\"test\",\r\n \"closed\":\"false\",\r\n \"idMemberCreator\":\"1000\",\r\n \"idOrganization\":\"1000\",\r\n \"pinned\":\"false\",\r\n \"url\":\"https:\/\/trello.com\/b\/dQHqCohZ\/trello-platform-changelog\",\r\n \"shortUrl\":\"https:\/\/trello.com\/b\/dQHqCohZ\",\r\n \"prefs\":{\r\n    \"permissionLevel\":\"org\",\r\n    \"hideVotes\":\"true\",\r\n    \"voting\":\"disabled\",\r\n    \"comments\":\"test\",\r\n    \"selfJoin\":\"true\",\r\n    \"cardCovers\":\"true\",\r\n    \"isTemplate\":\"true\",\r\n    \"cardAging\":\"pirate\",\r\n    \"calendarFeedEnabled\":\"true\",\r\n    \"background\":\"1000\",\r\n    \"backgroundImage\":\"test\",\r\n    \"backgroundImageScaled\":\"test\"\r\n },\r\n \"labelNames\":{\r\n    \"green\":\"Addition\",\r\n    \"yellow\":\"Update\",\r\n    \"orange\":\"Deprecation\",\r\n    \"red\":\"Deletion\",\r\n    \"purple\":\"Power-Ups\",\r\n    \"blue\":\"News\",\r\n    \"sky\":\"Announcement\",\r\n    \"lime\":\"Delight\",\r\n    \"pink\":\"REST API\",\r\n    \"black\":\"Capabilties\"\r\n },\r\n \"limits\":{\r\n    \"attachments\":{\r\n       \"perBoard\":{\r\n          \"status\":\"ok\",\r\n          \"disableAt\":36000,\r\n          \"warnAt\":32400\r\n       }\r\n    }\r\n },\r\n \"starred\":\"true\",\r\n \"memberships\":\"test\",\r\n \"shortLink\":\"test\",\r\n \"subscribed\":\"true\",\r\n \"powerUps\":\"test\",\r\n \"dateLastActivity\":\"test\",\r\n \"dateLastView\":\"test\",\r\n \"idTags\":\"test\",\r\n \"datePluginDisable\":\"test\",\r\n \"creationMethod\":\"test\",\r\n \"ixUpdate\":2154,\r\n \"templateGallery\":\"test\",\r\n \"enterpriseOwned\":\"true\"\r\n  }\r\n\r\n<\/pre>\n<p>After putting up everything you can run the test using <code>python -m pytest test_trello_get_board.py<\/code>. <\/p>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2020\/10\/Pact-Contract-Test-Console-3-Results.png\" data-rel=\"lightbox-image-1\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2020\/10\/Pact-Contract-Test-Console-3-Results.png\" alt=\"\" \/><\/a><\/p>\n<p>Note: I have run the test on <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/wsl\/install-win10\">WSL(Windows Subsystem for Linux)<\/a>.<\/p>\n<p>After the test runs successfully, you will find <code>consumer-provider.json<\/code> created. Here, you can verify response status as 200.The <code>consumer-provide.json<\/code> will look like as below:<\/p>\n<pre lang=\"json\">\r\n# consumer-provide.json\r\n{\r\n  \"consumer\": {\r\n    \"name\": \"Consumer\"\r\n  },\r\n  \"provider\": {\r\n    \"name\": \"Provider\"\r\n  },\r\n  \"interactions\": [\r\n    {\r\n      \"description\": \"a request for get trello board\",\r\n      \"providerState\": \"Response Payload will be received as expected\",\r\n      \"request\": {\r\n        \"method\": \"GET\",\r\n        \"path\": \"\/1\/boards\/1000\"\r\n      },\r\n      \"response\": {\r\n        \"status\": 200,\r\n        \"headers\": {\r\n        },\r\n        \"body\": {\r\n          \"id\": \"1000\",\r\n          \"desc\": \"Track changes to Trello's Platform on this board.\",\r\n          \"descData\": \"test\",\r\n          \"closed\": \"false\",\r\n          \"idMemberCreator\": \"1000\",\r\n          \"idOrganization\": \"1000\",\r\n          \"pinned\": \"false\",\r\n          \"url\": \"https:\/\/trello.com\/b\/dQHqCohZ\/trello-platform-changelog\",\r\n          \"shortUrl\": \"https:\/\/trello.com\/b\/dQHqCohZ\",\r\n          \"prefs\": {\r\n            \"permissionLevel\": \"org\",\r\n            \"hideVotes\": \"true\",\r\n            \"voting\": \"disabled\",\r\n            \"comments\": \"test\",\r\n            \"selfJoin\": \"true\",\r\n            \"cardCovers\": \"true\",\r\n            \"isTemplate\": \"true\",\r\n            \"cardAging\": \"pirate\",\r\n            \"calendarFeedEnabled\": \"true\",\r\n            \"background\": \"1000\",\r\n            \"backgroundImage\": \"test\",\r\n            \"backgroundImageScaled\": \"test\"\r\n          },\r\n          \"labelNames\": {\r\n            \"green\": \"Addition\",\r\n            \"yellow\": \"Update\",\r\n            \"orange\": \"Deprecation\",\r\n            \"red\": \"Deletion\",\r\n            \"purple\": \"Power-Ups\",\r\n            \"blue\": \"News\",\r\n            \"sky\": \"Announcement\",\r\n            \"lime\": \"Delight\",\r\n            \"pink\": \"REST API\",\r\n            \"black\": \"Capabilties\"\r\n          },\r\n          \"limits\": {\r\n            \"attachments\": {\r\n              \"perBoard\": {\r\n                \"status\": \"ok\",\r\n                \"disableAt\": 36000,\r\n                \"warnAt\": 32400\r\n              }\r\n            }\r\n          },\r\n          \"starred\": \"true\",\r\n          \"memberships\": \"test\",\r\n          \"shortLink\": \"test\",\r\n          \"subscribed\": \"true\",\r\n          \"powerUps\": \"test\",\r\n          \"dateLastActivity\": \"test\",\r\n          \"dateLastView\": \"test\",\r\n          \"idTags\": \"test\",\r\n          \"datePluginDisable\": \"test\",\r\n          \"creationMethod\": \"test\",\r\n          \"ixUpdate\": 2154,\r\n          \"templateGallery\": \"test\",\r\n          \"enterpriseOwned\": \"true\"\r\n        }\r\n      }\r\n    }\r\n  ],\r\n  \"metadata\": {\r\n    \"pactSpecification\": {\r\n      \"version\": \"2.0.0\"\r\n    }\r\n  }\r\n}\r\n\r\n<\/pre>\n<hr>\n<p>If you are a tester working on testing microservices then the above example will help you to add contract tests to your test strategy.<\/p>\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>This post shows you a short example of how to use the Python module Pact to write Consumer Contract tests against Provider Contract. We will use Trello API endpoints for writing sample test. Note that this blog will only cover one sample test. Testers can write many more for a different response such as unauthorized access, server not found. What [&hellip;]<\/p>\n","protected":false},"author":28,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43,259],"tags":[],"class_list":["post-13817","post","type-post","status-publish","format-standard","hentry","category-api-testing","category-contract-testing"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/13817","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=13817"}],"version-history":[{"count":48,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/13817\/revisions"}],"predecessor-version":[{"id":18082,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/13817\/revisions\/18082"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=13817"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=13817"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=13817"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}