{"id":880,"date":"2020-06-30T18:04:34","date_gmt":"2020-06-30T08:04:34","guid":{"rendered":"http:\/\/clickworks.me\/?p=880"},"modified":"2020-07-05T14:19:12","modified_gmt":"2020-07-05T04:19:12","slug":"tavern-testing-api-examples","status":"publish","type":"post","link":"https:\/\/clickworks.me\/index.php\/2020\/06\/30\/tavern-testing-api-examples\/","title":{"rendered":"Tavern API Testing Examples"},"content":{"rendered":"<p><img decoding=\"async\" src=\"tavern_header.png\" alt=\"\" \/><\/p>\n<h1>Tavern API Testing Examples<\/h1>\n<blockquote>\n<p>&#8220;Tavern is a <strong>pytest<\/strong> plugin, command-line tool and <strong>Python<\/strong> library for automated <strong>testing of APIs<\/strong>, with a simple, concise and flexible YAML-based syntax&#8221; (<a href=\"https:\/\/taverntesting.github.io\/\">https:\/\/taverntesting.github.io\/<\/a>)<\/p>\n<\/blockquote>\n<p>I created GitHub <a href=\"https:\/\/github.com\/MaksimZinovev\/tau-tools-demo\/tree\/master\/py-tavern-api\">repo<\/a> containing 30+ simple Tavern test examples and decided\u00a0 to share it on my website.\u00a0 I think it can helpful for someone like me who has just started to learn Tavern\u00a0 or looking for a\u00a0 tool to automate API testing. You can find lots of examples in Tavern Documentation, but most of them you cannot just copy-paste and run to see the result (some examples require running\u00a0 server\u00a0 using Flask). Therefore I created a set of my own examples using free public APIs to help me go through Tavern documentation and learn how to use it:<\/p>\n<ul>\n<li><a href=\"http:\/\/api.zippopotam.us\/\">http:\/\/api.zippopotam.us\/<\/a><\/li>\n<li><a href=\"https:\/\/jsonplaceholder.typicode.com\">https:\/\/jsonplaceholder.typicode.com<\/a><\/li>\n<li><a href=\"https:\/\/gorest.co.in\/\">https:\/\/gorest.co.in\/<\/a><\/li>\n<li><a href=\"http:\/\/www.recipepuppy.com\">http:\/\/www.recipepuppy.com<\/a><\/li>\n<li><a href=\"http:\/\/www.dropboxapi.com\">http:\/\/www.dropboxapi.com<\/a><\/li>\n<\/ul>\n<p>Some examples of Tavern tests:<\/p>\n<ul>\n<li>Sending request and checking response status code, json response, headers<\/li>\n<li>Using external function to validate response<\/li>\n<li>Using built-in Tavern schema validators<\/li>\n<li>Test parametrization<\/li>\n<li>Multi stage tests<\/li>\n<li>Using external configuration files<\/li>\n<li>Uploading file<\/li>\n<li>Creating and reading resources using APIs<\/li>\n<\/ul>\n<h1>Tavern Docs and useful links<\/h1>\n<p>Feel free to read <a href=\"https:\/\/tavern.readthedocs.io\/en\/latest\/index.html\">Tavern Documentation<\/a>. It is well written and has plenty of examples.<\/p>\n<p>There are not many articles and tutorials on how to use Tavern. Here are a few links that I found:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.ontestautomation.com\/writing-api-tests-in-python-with-tavern\/\">https:\/\/www.ontestautomation.com\/writing-api-tests-in-python-with-tavern\/<\/a><\/li>\n<li><a href=\"https:\/\/medium.com\/@ali.muhammadimran\/rest-api-test-automation-using-python-with-tavern-ci-part-1-707026eae702\">https:\/\/medium.com\/@ali.muhammadimran\/rest-api-test-automation-using-python-with-tavern-ci-part-1-707026eae702<\/a><\/li>\n<li><a href=\"https:\/\/apagiaro.it\/tavern-test-api\/\">https:\/\/apagiaro.it\/tavern-test-api\/<\/a><\/li>\n<\/ul>\n<h1>Getting Started<\/h1>\n<p><strong>If you clone this repo and use PyCharm:<\/strong><\/p>\n<ul>\n<li>clone repo<\/li>\n<li>set up virtual environment<\/li>\n<li>install dependancies (pip install -r requirements.txt)<\/li>\n<li>install colorlog\u00a0<\/li>\n<li>in PyCharm set pytest as default test runner (preferences-tools-python integrated tools-testing-pytest-apply)<\/li>\n<li>make sure your yaml test is called test_x.tavern.yaml, where x should be a description of the contained tests<\/li>\n<li>edit conftest.py and replace my API keys with yours:<\/li>\n<\/ul>\n<p>I used dotenv to inject my API keys in tests, so you will need to disable this section in conftest.py:<\/p>\n<pre><code class=\"language-python\"># conftest.py\n# =========== disable this section and obtain your own API keys to run tests on your machine ================\n# =========== see comments in test_basics.tavern.yaml, test_http.tavern.yaml ================================\ntry:\n    load_dotenv()\n    API_KEY_DROPBOX = os.getenv(&#039;API_KEY_DROPBOX&#039;)\n    API_KEY_GOREST = os.getenv(&#039;API_KEY_GOREST&#039;)\nexcept Exception as e:\n    logging.info(f&#039;disable try-except section at the top of conftest.py and obtain your own API keys to run tests &#039;\n                 f&#039;on your machine: {e}&#039;)\n\n# =====================================================================================================<\/code><\/pre>\n<p>Then replace variables\u00a0 with your API_KEYs, example:<\/p>\n<pre><code class=\"language-yaml\">\nstages:\n  - name: Authenticate and add new random user\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: POST\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY, example:\n        #  Authorization: &quot;Bearer 234dflkjdf967lkjdsf&quot;\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>If you start your own project and use PyCharm:<\/strong><\/p>\n<ul>\n<li>create new project<\/li>\n<li>set up virtual environment<\/li>\n<li>install tavern<\/li>\n<li>install colorlog<\/li>\n<li>in PyCharm set pytest as default test runner (preferences-tools-python integrated tools-testing-pytest-apply)<\/li>\n<li>make sure your yaml tests are names use the following pattern test_x.tavern.yaml, where x should be a description of the contained tests<\/li>\n<li>feel free to use my contest.py and pytest.ini to make test output friendlier.<\/li>\n<\/ul>\n<h1>Project structure example<\/h1>\n<p>Below is the structure of my project folder<\/p>\n<pre><code class=\"language-bash\">.\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 __pycache__\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 conftest.cpython-38-pytest-5.4.1.pyc\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 conftest.cpython-38-pytest-5.4.2.pyc\n\u251c\u2500\u2500 accounts.yaml\n\u251c\u2500\u2500 conftest.py\n\u251c\u2500\u2500 logging.yaml\n\u251c\u2500\u2500 pytest.ini\n\u251c\u2500\u2500 readme.md\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 tavern-demo.gif\n\u251c\u2500\u2500 test_data\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 test_pdf.pdf\n\u251c\u2500\u2500 tests\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 __pycache__\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 api_urls.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 common.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 includes.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 includesA.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 includesB.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_basics.tavern.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 test_http.tavern.yaml\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 utils.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 yaml_basics.yaml\n\u251c\u2500\u2500 venv\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 bin\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 include\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 lib\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 pyvenv.cfg\n\u2514\u2500\u2500 zip_code.yaml\n<\/code><\/pre>\n<h1>Some common mistakes to avoid<\/h1>\n<p>Of course you can read the docs, but I decided to share some of my mistakes. Chances are you have the same problem which can be fixed quite quickly if you know where to look at.<\/p>\n<h1>Adding folder to PYTHONPATH<\/h1>\n<p>To make sure that Tavern can find <strong>external functions<\/strong> you need to make sure that it is in the Python path. I had some issues with adding my test dir to PYTHONPATH. Seems like in different shells this\u00a0 command may vary. This is what works \/ does not work for me (I use zsh). For example, if <strong>utils.py<\/strong> is in the \u2018tests\u2019 folder:<\/p>\n<p><strong>Not worked:<\/strong><\/p>\n<pre><code class=\"language-bash\"> PYTHONPATH=$PYTHONPATH:\/tests pytest tests\/test_basics.tavern.yaml -k ex04\n ...\n E   tavern.util.exceptions.InvalidExtFunctionError: Error importing module utils\n<\/code><\/pre>\n<pre><code class=\"language-bash\">PYTHONPATH=$PYTHONPATH:tests pytest tests\/test_basics.tavern.yaml -k ex04\n ...\nzsh: bad substitution<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Worked<\/strong>:<\/p>\n<pre><code class=\"language-bash\"> PYTHONPATH=$PYTHONPATH:.\/tests pytest tests\/test_basics.tavern.yaml -q -k ex04<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>You can modify ~\/.bash_profile to add absolute path to your PYTHONPATH so you do not need to include PYTHONPATH in command each time (note, this might affect other projects, so just comment it out when you do not need it) . For Example:<\/p>\n<pre><code class=\"language-bash\">export PYTHONPATH=&quot;$PYTHONPATH:\/Users\/maksim\/repos\/tau-tools-demo\/py-tavern-api\/tests&quot;\n# save and exit\n# then run in shell or in PyCharm terminal:\nsource ~\/.bashprofile<\/code><\/pre>\n<h1>Printing entire response using logging<\/h1>\n<p>Add this hook to your contest.py file to be able to see response even if it returned not in json format. Very often it helps with debugging:<\/p>\n<pre><code class=\"language-python\">#conftest.py\n\ndef pytest_tavern_beta_after_every_response(expected, response):\n    try:\n        logging.info(f&quot;================= RESPONSE ================== &quot;\n                     f&quot;\\n\\nstatus code [{response.status_code}]\\n{dumps(response.json(), indent=4)}\\n\\n&quot;)\n\n    except ValueError as e:\n        logging.info(f&quot;================= RESPONSE ================== &quot;\n                     f&quot;\\n\\nstatus code [{response.status_code}]\\n{response.text,}\\n\\n&quot;)\n    return<\/code><\/pre>\n<p><strong>Use &#8211;log-cli-level in command line to enable different logging levels. Example<\/strong><\/p>\n<pre><code class=\"language-markup\">pytest --log-cli-level=ERROR  snippets\/test_basics.tavern.yaml<\/code><\/pre>\n<p><strong>Can also use pytest.ini to set logging level:<\/strong><\/p>\n<pre><code class=\"language-markup\">log_cli = 1\nlog_level = INFO\nlog_cli_level = INFO<\/code><\/pre>\n<p>Example of pytest.ini<\/p>\n<p>Here is what I have in my pytest.ini file<\/p>\n<pre><code class=\"language-markup\">[pytest]\ntavern-global-cfg=\n    snippets\/common_snippets.yaml\n    tests\/common.yaml\n    tests\/secrets.yaml\n    snippets\/api_urls.yaml\ntavern-strict=json:off\ntavern-beta-new-traceback = True\n\nfilterwarnings =\n    ignore::UserWarning\n    ignore::ImportWarning\n    ignore::ResourceWarning\n\ntestpaths = \u200btests\u200b, snippets\naddopts =\n    --doctest-modules\n    -r xs\n    -p no:warnings\n    -vv\n    --tb=short\n\nlog_cli = 1\nlog_level = INFO\nlog_cli_level = INFO\nlog_cli_format = %(asctime)s %(levelname)s %(message)s\nlog_cli_date_format = %H:%M:%S\n\n<\/code><\/pre>\n<h1>Test Examples<\/h1>\n<p>Simple request and <strong>validate status code:<\/strong><\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Sending request and checking response status code ex01\n\nstages:\n  - name: Check that HTTP status code equals 200\n    request:\n      url: http:\/\/api.zippopotam.us\/us\/90210\n      method: GET\n    response:\n      status_code: 200\n<\/code><\/pre>\n<p><strong>Request variables<\/strong><\/p>\n<pre><code class=\"language-yaml\">---\ntest_name: Using request variables ex02\nstages:\n  - name: Create a resource, verify response json using request variables\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\n      method: POST\n      json:\n        title: foo\n        body: bar\n        userId: 1\n      headers:\n        content-type: application\/json; charset=UTF-8\n\n    response:\n      status_code:\n        - 201\n#        - 503\n      json:\n        title: &quot;{tavern.request_vars.json.title}&quot;\n        body: &quot;{tavern.request_vars.json.body}&quot;\n        userId: !int &quot;{tavern.request_vars.json.userId}&quot;\n        id: 101<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Environment variables<\/strong>. Before running the test update ~\/.bash_profile:<\/p>\n<pre><code class=\"language-yaml\">---\n# export TITLE=&quot;foo&quot;\n# export PYTHONPATH=&quot;$PYTHONPATH:\/Users\/maksim\/repos\/tau-tools-demo\/py-tavern-api\/tests&quot;\n# source  ~\/.bash_profile\n\n\ntest_name: Using  environment variables ex03\n\nstages:\n  - name: Create a resource, use environment variables to validate response\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\n      method: POST\n      json:\n        title: foo\n        body: bar\n        userId: 1\n      headers:\n        content-type: application\/json; charset=UTF-8\n\n    response:\n      status_code: 201\n      json:\n        title: &quot;{tavern.env_vars.TITLE}&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Checking the response using <strong>external functions<\/strong>. Note: you can only use an external function to create the whole body or response &#8211; #if you just want one value, you should use a pytest fixture (an example is in the documentation).<\/p>\n<p>Before running the test update~\/.bash_profile. Example:<\/p>\n<pre><code class=\"language-yaml\">\n# export PYTHONPATH=&quot;Users\/maksim\/repos\/tau-tools-demo\/py-tavern-api\/tests&quot;\n# to avoid affecting  other projects after you finished work in Tavern project comment out\/delete the above line and run:\n# unset PYTHONPATH ; source ~\/.bash_profile\n\n#\n\ntest_name: Using external function to validate response ex04\nstages:\n  - name: Make sure we have the right ID\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\/1\n      method: GET\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: utils:get_id<\/code><\/pre>\n<p>Here is get_id function:<\/p>\n<pre><code class=\"language-python\"># conftest.py\ndef get_id(response):\n    # Make sure that  id=1 in the response\n\n    assert response.json().get(&quot;id&quot;) == 1\n    assert response.json().get(&quot;userId&quot;) == 1<\/code><\/pre>\n<p>Using external function with extra arguments to validate response:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Using external function with extra arguments to validate response ex04a\nstages:\n  - name: Make sure we have the right ID\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\/1\n      method: GET\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: utils:demo_extra_kwargs\n        extra_kwargs:\n          arg1: &quot;hello&quot;\n          arg2: &quot;world&quot;\n<\/code><\/pre>\n<p>Here is demo_extra_kwargs function:<\/p>\n<pre><code class=\"language-yaml\">def demo_extra_kwargs(response, arg1, arg2):\n    # Make sure that arg1 received\n\n    logging.info(f&#039;\\nArg1: {arg1}&#039;)\n    logging.info(f&#039;\\nArg2: {arg2}&#039;)<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Using <strong>built-in validators<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Using built-in validators ex05\nstages:\n  - name: Make sure the response matches the given schema\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\/1\n      method: GET\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: tavern.testutils.helpers:validate_pykwalify\n        extra_kwargs:\n          schema:\n            type: map\n            mapping:\n              id:\n                type: int\n                required: True\n              title:\n                type: any\n                required: True\n              body:\n                type: any\n                required: True\n              userId:\n                type: any\n                required: True\n\n#  Expected\n#{\n#  id: 1,\n#  title: &#039;[...]&#039;,\n#  body: &#039;[...]&#039;,\n#  userId: 1\n#}<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Using <strong>built-in validator<\/strong>. The dict can have keys which are not present in the schema, and these can map to anything. Usage: allowempty: True<br \/>\nSee documentation here https:\/\/pykwalify.readthedocs.io\/en\/unstable\/validation-rules.html#mapping<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Using built-in validators and allowempty ex06\nstages:\n  - name: Make sure the response matches the given schema\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\/1\n      method: GET\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: tavern.testutils.helpers:validate_pykwalify\n        extra_kwargs:\n          schema:\n            type: map\n            allowempty: True\n            mapping:\n              id:\n                type: int\n                required: True\n              title:\n                type: any\n                required: True<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Simple request and <strong>validate headers:<\/strong><\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Get location for US zip code 90210 and validate response content type is equal to \u2018application\/json\u2019 ex07\n\nstrict:\n  - headers:off\n  - json:off\n\nstages:\n  - name: Check that HTTP status code equals 200 and other fields\n    request:\n      url: http:\/\/api.zippopotam.us\/us\/90210\n      method: GET\n\n    response:\n      headers:\n        content-type: application\/json\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Simple request and <strong>validate json content<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\n# \ntest_name: Get location for US zip code 90210 and check response body content ex08\n\nstages:\n  - name: Check that place name equals Beverly Hills\n    request:\n      url: http:\/\/api.zippopotam.us\/us\/90210\n      method: GET\n\n    response:\n      json:\n        places:\n          - place name: Beverly Hills<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>How to use <strong>parametrization<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">\ntest_name: Using parametrization in test ex09\nmarks:\n  - parametrize:\n      key:\n        - country_code\n        - zip_code\n        - place_name\n      vals:\n        - [us, 12345, Schenectady]\n        - [ca, B2A, North Sydney South Central]\n        - [nl, 3825, Vathorst]\n\nstages:\n  - name: Verify place name in response body\n    request:\n      url: http:\/\/api.zippopotam.us\/{country_code}\/{zip_code}\n      method: GET\n    response:\n      json:\n        places:\n          - place name: &quot;{place_name}&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Using <strong>external functions<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Injecting external data into a request using external functions for other things ex10\nstages:\n  - name: Injecting external data into a request\n\n    request:\n      url: http:\/\/www.recipepuppy.com\/api\/\n      method: GET\n      json:\n        $ext:\n          function: utils:generate_req1\n\n    response:\n      status_code: 200\n      json:\n        title: Recipe Puppy\n        version:  0.1\n        results:\n          - title: &quot;Ginger Champagne&quot;\n            ingredients: &quot;champagne, ginger, ice, vodka&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Here is external function\u00a0 generate_req1:<\/p>\n<pre><code class=\"language-python\"># tests\/utils.py\n\nfrom box import Box\n\ndef generate_req1():\n\n    req = {\n    &quot;i&quot;: &quot;avocado&quot;,\n    &quot;q&quot;: &quot;kale&quot;,\n    &quot;p&quot;: 1,\n        }\n\n    return Box(req)<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Saving data from response<\/strong> using external functions. In this case, both `{test_title}` and `{test_ingredients}` are available for use in later requests:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Saving data from response using external functions ex11\nstages:\n  - name: Validate status code 200\n\n    request:\n      url: http:\/\/www.recipepuppy.com\/api\/\n      method: GET\n      json:\n        i: avocado\n        q: kale\n        p: 1\n\n    response:\n      json:\n        title: &quot;Recipe Puppy&quot;\n      save:\n        $ext:\n          function: utils:save_data\n        json:\n          test_ingredients: results[0].ingredients\n\n  - name: Validate saved  data\n\n    request:\n      url: http:\/\/www.recipepuppy.com\/api\/\n      method: GET\n      json:\n        i: avocado\n        q: kale\n        p: 1\n\n    response:\n      status_code: 200\n      json:\n        results:\n          - title: &quot;{test_title}&quot;\n            ingredients: &quot;{test_ingredients}&quot;\n\n<\/code><\/pre>\n<p>Here is save_data function:<\/p>\n<pre><code class=\"language-python\"># tests\/utils.py\nfrom box import Box\ndef save_data(response):\n    return Box({&quot;test_title&quot;: response.json()[&quot;results&quot;][0][&quot;title&quot;]})<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Reusing requests and YAML fragments:<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Reusing requests and YAML fragments part1  ex12\nstages:\n  - name: &amp;name_block Send request\n    request: &amp;request_block\n      url: http:\/\/www.recipepuppy.com\/api\/?i=avocado&amp;q=kale&amp;p=1\n      method: GET\n<\/code><\/pre>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Reusing requests and YAML fragments part2 ex13\nstages:\n  - name: Reusing req block\n    request:\n      *request_block\n    response:\n      status_code: 200\n      json:\n        title: Recipe Puppy\n        version:  0.1\n        results:\n          - title: &quot;A Ok Salad Recipe&quot;\n            ingredients: &quot;cayenne, lemon, avocado, kale, sea salt, tomato&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Including external configuration files<\/strong>, using common.yaml, \u00a0includes.yaml should be in the same folder as the tests<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Including external files ex15\nincludes:\n  - !include includes.yaml\n\nstages:\n  - name: Check that HTTP status code equals 200\n    request:\n      url: &quot;{protocol1}:\/\/{host_gorest}{res_users}&quot;\n      method: GET\n    response:\n      status_code: 200\n      json:\n        result:\n          name: Unauthorized\n          code: 0\n          status: 401<\/code><\/pre>\n<pre><code class=\"language-yaml\"># tests\/includes.yaml\n---\n\nname: Common test information\ndescription: Some information for tests\n\n# Variables should just be a mapping of key: value pairs\nvariables:\n  protocol: https\n  host_gorest: gorest.co.in\n  res_users: \/public-api\/users<\/code><\/pre>\n<pre><code class=\"language-yaml\"># tests\/common.yaml\n---\n\nname: Common test information\ndescription: Some information for tests\n\n# Variables should just be a mapping of key: value pairs\nvariables:\n\n  protocol1: https\n  host_gorest: gorest.co.in\n  res_users: \/public-api\/users\n\n  protocol2: http\n  host_rp: www.recipepuppy.com\n  res_rp: \/api\/\n\n  host_zipo: api.zippopotam.us\n  api-arg: !raw &#039;{&quot;path&quot;: &quot;\/test_pdf.pdf&quot;, &quot;mode&quot;: &quot;add&quot;,&quot;autorename&quot;: true, &quot;mute&quot;: false, &quot;strict_conflict&quot;: false}&#039;\n  # this is path in your Dropbox folder. In this case file  will be uploaded to the root folder\n\n\nstages:\n  - id: add_rand_user\n    name: Authenticate and add new random user\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: POST\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n    response:\n      status_code:\n        - 200\n        - 302 # the requested resource has been temporarily moved to a different URI\n      json:\n        _meta:\n          message: &quot;A resource was successfully created in response to a POST request. The Location header contains the URL pointing to the newly created resource.&quot;\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id: result.id\n\n  - id: update_user_rand\n    name: Update user\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: PUT\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n    response:\n      status_code:\n        - 200\n        - 302 # the requested resource has been temporarily moved to a different URI\n      json:\n        _meta:\n          message: &quot;OK. Everything worked as expected.&quot;\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id_upd: result.id\n\n  - id: delete_user\n    name: Delete user\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: DELETE\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n\n      json:\n        _meta:\n          code: 204\n          message: &quot;The request was handled successfully and the response contains no body content.&quot;\n\n<\/code><\/pre>\n<p>Also pytest.ini can be used to include external files:<\/p>\n<pre><code class=\"language-markup\"># [pytest]\n# tavern-global-cfg=tests\/common.yaml\n# tavern-global-cfg=tests\/includesA.yaml\n# tavern-global-cfg=tests\/includesB.yaml\n<\/code><\/pre>\n<p>Including global configuration files:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Including  global configuration files part1 ex16\n\nincludes:\n  - !include includesA.yaml\n\nstages:\n  - name: Send request\n    request:\n      url: &quot;{protocol2}:\/\/{host_rp}{res_rp}{query1}&quot;\n      method: GET\n    response:\n          status_code: 200\n          json:\n            results:\n              - title: A Ok Salad Recipe\n\n---\ntest_name: Including  global configuration files part2 ex17\n\nincludes:\n  - !include includesB.yaml\n\nstages:\n  - name: Send request\n    request:\n      url: &quot;{protocol2}:\/\/{host_rp}{res_rp}{query2}&quot;\n      method: GET\n    response:\n          status_code: 200\n          json:\n            results:\n              - title: Chocolate-chocolate Chip Banana Muffins Recipe\n              - title: Chocolate Banana Chocolate Chip Bundt Cake\n              - title: Banana Fritters in Chocolate Batter And Chocolate Sauce<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Authentication example<\/strong>. Using saved variable. Using external function to generate request you can only use an external function to create the whole body or response &#8211; if you just want one value, you should use a pytest fixture (an example is in the documentation).<\/p>\n<p>\u00a0<strong>Note<\/strong> that there is also no way to use a fixture to generate a block of json &#8211; either use $ext to generate the whole<br \/>\nrequest, or a fixture to generate one value. Anything in between is currently not implemented (see issue#191 in github).<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Authenticate and add new random user ex19\n\nstages:\n  - name: Authenticate and add new random user\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: POST\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n\n    response:\n      status_code:\n        - 200\n        - 302\n      json:\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id: result.id\n\n  - name: Check user&#039;s details\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: GET\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: utils:log_response\n      json:\n        result:\n          first_name: Max\n          gender: male\n          id: &quot;{user_id}&quot;\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Sharing stages<\/strong> in configuration files:<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Sharing stages in configuration   ex20\n\nstages:\n  - type: ref\n    id: add_rand_user\n\n  - name: Check user&#039;s details\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: GET\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n      verify_response_with:\n        function: utils:log_response\n      json:\n        result:\n          first_name: Max\n          gender: male\n          id: &quot;{user_id}&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Here is<strong> stage<\/strong> defined in common.yaml:<\/p>\n<pre><code class=\"language-yaml\"># tests\/common.yaml\n\nstages:\n  - id: add_rand_user\n    name: Authenticate and add new random user\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: POST\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n    response:\n      status_code:\n        - 200\n        - 302 # the requested resource has been temporarily moved to a different URI\n      json:\n        _meta:\n          message: &quot;A resource was successfully created in response to a POST request. The Location header contains the URL pointing to the newly created resource.&quot;\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id: result.id\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Matching arbitrary return values<\/strong> in a response:;<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Match arbitrary return values ex21\n\nstages:\n  - name: Check value types in user details\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: GET\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n      json:\n        result:\n          - first_name: !anystr\n            status: active\n            id: !anystr<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Matching via a regular expression<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name:  Matching via a regular expression ex22\n\nstages:\n  - type: ref\n    id: add_rand_user\n\n  - name: Check value types in user details\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: GET\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n      json:\n        result:\n#          - first_name: !re_fullmatch &quot;[A-Za-z0-9]&quot;\n          id: !re_search &quot;[0-9]&quot;\n          first_name: !re_search &quot;[A-Za-z0-9]&quot;\n          email: !re_search &quot;[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[a-zA-Z0-9-.]&quot;\n          status: active\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Adding a <strong>delay between tests<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Adding a delay between tests ex23\n\nstages:\n  - type: ref\n    id: add_rand_user\n\n  - type: ref\n    id: update_user_rand\n\n\n  - name: Check user&#039;s details\n    delay_before: 4\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id_upd}\n      method: GET\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n\n    response:\n      status_code: 200\n      json:\n        result:\n          first_name: Max\n          gender: male\n          id: &quot;{user_id_upd}&quot;<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>add_rand_user and update_user_rand stages are defined in common.yaml:<\/p>\n<pre><code class=\"language-yaml\">#common.yaml\nstages:\n  - id: add_rand_user\n    name: Authenticate and add new random user\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\n      method: POST\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n    response:\n      status_code:\n        - 200\n        - 302 # the requested resource has been temporarily moved to a different URI\n      json:\n        _meta:\n          message: &quot;A resource was successfully created in response to a POST request. The Location header contains the URL pointing to the newly created resource.&quot;\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id: result.id\n\n  - id: update_user_rand\n    name: Update user\n\n    request:\n      url: https:\/\/gorest.co.in\/public-api\/users\/{user_id}\n      method: PUT\n      headers:\n        Content-Type: application\/json\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_GOREST}&quot;\n        #  replace above variable with your API_KEY\n        #  go to https:\/\/gorest.co.in\/user\/login.html to register free account\n      json:\n        $ext:\n          function: utils:generate_req_rand\n\n    response:\n      status_code:\n        - 200\n        - 302 # the requested resource has been temporarily moved to a different URI\n      json:\n        _meta:\n          message: &quot;OK. Everything worked as expected.&quot;\n        result:\n          id: !anystr\n      save:\n        json:\n          user_id_upd: result.id<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Retrying tests<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Retrying tests ex24\nstages:\n  - name: Create a resource\n    max_retries: 2  # retry a stage 2 times\n    request:\n      url: https:\/\/jsonplaceholder.typicode.com\/posts\n      method: POST\n      json:\n        title: foo\n        body: bar\n        userId: 2\n      headers:\n        content-type: application\/json; charset=UTF-8\n\n    response:\n      status_code: 201\n<\/code><\/pre>\n<p># <strong>Marking tests<\/strong>. Make sure pytest.ini does not contain &#8221; addopts = &#8211;strict&#8221; otherwise add markers names:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Marking test ex25\n\nmarks:\n  - gorest\n\nstages:\n  - type: ref\n    id: add_rand_user\n\n  - type: ref\n    id: delete_user<\/code><\/pre>\n<p><strong>Skipping a test<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\n\ntest_name: Skipping a test ex26\n\nmarks:\n  - skip\n\nstages:\n  - name: Check that HTTP status code equals 200 and other fields\n    request:\n      url: http:\/\/api.zippopotam.us\/us\/90211\n      method: GET\n\n    response:\n      headers:\n        content-type: application\/json\n\n---\n\n\ntest_name: Skipping a test using skipif ex27\n\nmarks:\n  - skipif: &quot;&#039;api&#039; in &#039;{host_zipo}&#039;&quot;\n\nstages:\n  - name: Check that HTTP status code equals 200 and other fields\n    request:\n      url: &quot;http:\/\/{host_zipo}\/us\/90211&quot;\n      method: GET\n\n    response:\n      headers:\n        content-type: application\/json<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Using pytest fixtures:<\/strong><\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Using fixtures ex29\n\nmarks:\n  - usefixtures:\n      - zipcode # fixture is defined in conftest.py\n\nstages:\n  - name: Read zip code from file and use in request\n    request:\n      url: &quot;http:\/\/{host_zipo}\/us\/{zipcode}&quot;\n      method: GET\n\n    response:\n      headers:\n        content-type: application\/json\n      status_code: 200<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Fixture is defined in conftest.py:<\/p>\n<pre><code class=\"language-python\">#conftest.py\n\nimport logging\nimport os\nimport html\nimport yaml\n\n@pytest.fixture\ndef zipcode():\n    current_dir = os.path.dirname(os.path.abspath(__file__))\n    with open(os.path.join(current_dir, &quot;zip_code.yaml&quot;), &quot;r&quot;) as file:\n        zcode = yaml.load(file, Loader=yaml.FullLoader)\n        file.close()\n    logging.info(f&#039;Code: {zcode[&quot;code&quot;]}&#039;)\n    return zcode[&quot;code&quot;]<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Using hooks<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">---\n\ntest_name: Using hook pytest_tavern_beta_after_every_response ex30\n# hook is defined in conftest.py and logs out response\n\nmarks:\n  - usefixtures:\n      - zipcode # fixture defined in conftest.py\n\nstages:\n  - name: Read zip code from file and use in request\n    request:\n      url: &quot;http:\/\/{host_zipo}\/us\/{zipcode}&quot;\n      method: GET\n\n    response:\n      headers:\n        content-type: application\/json<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Here is hook pytest_tavern_beta_after_every_response defined in conftest.py:<\/p>\n<pre><code class=\"language-python\">def pytest_tavern_beta_after_every_response(expected, response):\n    try:\n        logging.info(f&quot;================= RESPONSE ================== &quot;\n                     f&quot;\\n\\nstatus code [{response.status_code}]\\n{dumps(response.json(), indent=4)}\\n\\n&quot;)\n\n    except ValueError as e:\n        if &quot;!DOCTYPE html&quot; in response.text:\n            logging.info(f&quot;================= RESPONSE ================== &quot;\n                         f&quot;\\n\\nstatus code [{response.status_code}]\\n{html.unescape(response.text)}\\n\\n&quot;)\n        else:\n            logging.info(f&quot;================= RESPONSE ================== &quot;\n                        f&quot;\\n\\nstatus code [{response.status_code}]\\n{response.text,}\\n\\n&quot;)\n    return<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Authenticate and get current Dropbox account<\/strong>:<\/p>\n<pre><code class=\"language-yaml\">test_name: Authenticate and get current Dropbox account info ex31\n\nstages:\n  - name: Authenticate and check that HTTP status code equals 200\n    request:\n      url: https:\/\/api.dropboxapi.com\/2\/users\/get_current_account\n      method: POST\n      headers:\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_DROPBOX}&quot;\n        #  replace the above variable with your API_KEY\n        # go to https:\/\/www.dropbox.com\/developers\/apps &gt; create app &gt; Dropbox API &gt; Full dropbox &gt; appname &gt; create app\n\n    response:\n      status_code: 200\n      json:\n        account_id: !anystr\n        name:\n          display_name: !anystr\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Get the contents of Dropbox root folder:<\/p>\n<pre><code class=\"language-yaml\">---\n# Simple request and validate status code\n\n\ntest_name: Get the contents of Dropbox root folder ex32\n\nstages:\n  - name: Authenticate and get the contents of Dropbox root folder\n    request:\n      url: https:\/\/api.dropboxapi.com\/2\/files\/list_folder\n      method: POST\n      headers:\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_DROPBOX}&quot;\n        #  replace the above variable with your API_KEY\n        # go to https:\/\/www.dropbox.com\/developers\/apps &gt; create app &gt; Dropbox API &gt; Full dropbox &gt; appname &gt; create app\n\n        Content-Type: application\/json\n      json:\n        path: &quot;&quot;\n        recursive: false\n        include_media_info: false\n        include_deleted: false\n        include_has_explicit_shared_members: false\n        include_mounted_folders: true\n        include_non_downloadable_files: true\n\n    response:\n      status_code: 200\n      json:\n        entries: !anylist<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>Upload file using Dropbox API. Uploaded file located in test_data\/test_pdf.pdf:<\/p>\n<pre><code class=\"language-yaml\">---\ntest_name: Upload file to Dropbox  folder (file body) ex33\n\nstages:\n  - name: Upload file to Dropbox folder\n    request:\n      url: https:\/\/content.dropboxapi.com\/2\/files\/upload\n      method: POST\n      file_body: &quot;test_data\/test_pdf.pdf&quot;\n\n      headers:\n        Authorization: &quot;Bearer {tavern.env_vars.API_KEY_DROPBOX}&quot;  #  replace the above variable with your API_KEY\n        Content-Type: application\/octet-stream\n        Dropbox-API-Arg: &quot;{api-arg}&quot;\n\n    response:\n      status_code: 200\n<\/code><\/pre>\n<p>&nbsp;<\/p>\n<pre><code class=\"language-yaml\">#tests\/common.yaml\n\napi-arg: !raw &#039;{&quot;path&quot;: &quot;\/test_pdf.pdf&quot;, &quot;mode&quot;: &quot;add&quot;,&quot;autorename&quot;: true, &quot;mute&quot;: false, &quot;strict_conflict&quot;: false}&#039;\n  # this is path in your Dropbox folder. In this case file  will be uploaded to the root folder\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Tavern API Testing Examples &#8220;Tavern is a pytest plugin, command-line tool and Python library for automated testing of APIs, with a simple, concise and flexible YAML-based syntax&#8221; (https:\/\/taverntesting.github.io\/) I created GitHub repo containing 30+ simple Tavern test examples and decided\u00a0 to share it on my website.\u00a0 I think it can helpful for someone like me who has just started to learn Tavern\u00a0 or looking for a\u00a0 tool to automate API<\/p>\n<div class=\"read-more\"><a class=\"btn read-more-btn\" href=\"https:\/\/clickworks.me\/index.php\/2020\/06\/30\/tavern-testing-api-examples\/\">Read More<\/a><\/div>\n","protected":false},"author":1,"featured_media":889,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[57],"tags":[58,56,45,67,68],"post_folder":[],"_links":{"self":[{"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/posts\/880"}],"collection":[{"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/comments?post=880"}],"version-history":[{"count":5,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/posts\/880\/revisions"}],"predecessor-version":[{"id":891,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/posts\/880\/revisions\/891"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/media\/889"}],"wp:attachment":[{"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/media?parent=880"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/categories?post=880"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/tags?post=880"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/clickworks.me\/index.php\/wp-json\/wp\/v2\/post_folder?post=880"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}