Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python pytest for testing the requests and response

I am a beginner to using pytest in python and trying to write test cases for the following method which get the user address when correct Id is passed else rises custom error BadId.

    def get_user_info(id: str, host='127.0.0.1', port=3000 ) -> str:
     uri = 'http://{}:{}/users/{}'.format(host,port,id)
     result = Requests.get(uri).json()
     address = result.get('user',{}).get('address',None)
     if address:
       return address
     else:
       raise BadId

Can someone help me with this and also can you suggest me what are the best resources for learning pytest? TIA

like image 427
lanchana gupta Avatar asked Aug 28 '17 00:08

lanchana gupta


Video Answer


1 Answers

Your test regimen might look something like this.

First I suggest creating a fixture to be used in your various method tests. The fixture sets up an instance of your class to be used in your tests rather than creating the instance in the test itself. Keeping tasks separated in this way helps to make your tests both more robust and easier to read.

from my_package import MyClass
import pytest

@pytest.fixture
def a_test_object():
    return MyClass()

You can pass the test object to your series of method tests:

def test_something(a_test_object):
    # do the test

However if your test object requires some resources during setup (such as a connection, a database, a file, etc etc), you can mock it instead to avoid setting up the resources for the test. See this talk for some helpful info on how to do that.

By the way: if you need to test several different states of the user defined object being created in your fixture, you'll need to parametrize your fixture. This is a bit of a complicated topic, but the documentation explains fixture parametrization very clearly.

The other thing you need to do is make sure any .get calls to Requests are intercepted. This is important because it allows your tests to be run without an internet connection, and ensures they do not fail as a result of a bad connection, which is not the thing you are trying to test.

You can intercept Requests.get by using the monkeypatch feature of pytest. All that is required is to include monkeypatch as an input parameter to the test regimen functions.

You can employ another fixture to accomplish this. It might look like this:

import Requests
import pytest

@pytest.fixture
def patched_requests(monkeypatch):
    # store a reference to the old get method
    old_get = Requests.get
    def mocked_get(uri, *args, **kwargs):
        '''A method replacing Requests.get
        Returns either a mocked response object (with json method)
        or the default response object if the uri doesn't match
        one of those that have been supplied.
        '''
        _, id = uri.split('/users/', 1)
        try:
            # attempt to get the correct mocked json method
            json = dict(
            with_address1 = lambda: {'user': {'address': 123}},
            with_address2 = lambda: {'user': {'address': 456}},
            no_address = lambda: {'user': {}},
            no_user = lambda: {},
            )[id]
        except KeyError:
            # fall back to default behavior
            obj = old_get(uri, *args, **kwargs)
        else:
            # create a mocked requests object
            mock = type('MockedReq', (), {})()
            # assign mocked json to requests.json
            mock.json = json
            # assign obj to mock
            obj = mock
        return obj
    # finally, patch Requests.get with patched version
    monkeypatch.setattr(Requests, 'get', mocked_get)

This looks complicated until you understand what is happening: we have simply made some mocked json objects (represented by dictionaries) with pre-determined user ids and addresses. The patched version of Requests.get simply returns an object- of type MockedReq- with the corresponding mocked .json() method when its id is requested.

Note that Requests will only be patched in tests that actually use the above fixture, e.g.:

def test_something(patched_requests):
    # use patched Requests.get

Any test that does not use patched_requests as an input parameter will not use the patched version.

Also note that you could monkeypatch Requests within the test itself, but I suggest doing it separately. If you are using other parts of the requests API, you may need to monkeypatch those as well. Keeping all of this stuff separate is often going to be easier to understand than including it within your test.

Write your various method tests next. You'll need a different test for each aspect of your method. In other words, you will usually write a different test for the instance in which your method succeeds, and another one for testing when it fails.

First we test method success with a couple test cases.

@pytest.mark.parametrize('id, result', [
    ('with_address1', 123),
    ('with_address2', 456),
])
def test_get_user_info_success(patched_requests, a_test_object, id, result):
    address = a_test_object.get_user_info(id)
    assert address == result

Next we can test for raising the BadId exception using the with pytest.raises feature. Note that since an exception is raised, there is not a result input parameter for the test function.

@pytest.mark.parametrize('id', [
    'no_address',
    'no_user',
])
def test_get_user_info_failure(patched_requests, a_test_object, id):
    from my_package import BadId
    with pytest.raises(BadId):
        address = a_test_object.get_user_info(id)

As posted in my comment, here also are some additional resources to help you learn more about pytest:

link

link

Also be sure to check out Brian Okken's book and Bruno Oliveira's book. They are both very helpful for learning pytest.

like image 160
Rick supports Monica Avatar answered Nov 15 '22 20:11

Rick supports Monica