0

I have a class in foo.py that I wish to test:

import requests

class Foo:
    def fooMethod(self, url):
        response = requests.get(url)
        return response

I want to replace the requests call to simulate the response.

Here is my test file in test_foo.py:

from foo import Foo

def mocked_requests_get(*args, **kwargs):
    class MockResponse:
        def __init__(self, text, code):
            self.text = text
            self.code = code
    
    if args[0] == "http://localhost":
        return MockResponse("Test response", 200)

    return MockResponse(None, 404)


class TestFoo:
    def test_foo(self, mocker):
        a = Foo()
        mocker.patch ('foo.requests.get', mocked_requests_get)
        spy = mocker.spy (a, 'test_foo.mocked_requests_get')
        response = a.fooMethod("http://localhost")
        assert response.text == "Test response"
        assert spy.call_count == 1

I want to check that the mocked_requests_get function is called only once.

The interpreter gives an error on the spy = mocker.spy ... line:

'Foo' object has no attribute 'test_foo.mocked_requests_get'

This is understandable - but I can't work out a way to get to the object instance that references that function. Can anyone help please?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
engeeaitch
  • 81
  • 3
  • 11

1 Answers1

2

Your approach is a bit too complicated - you can just use a standard mock without the need to implement your own mock class. Something like this should work:

class TestFoo:
    def test_foo(self, mocker):
        a = Foo()
        get_mock = mocker.patch('requests.get')
        get_mock.return_value.text = "Test response"
        response = a.fooMethod()
        assert response.text == "Test response"
        assert get_mock.call_count == 1

Also note that you have to patch requests.get instead of foo.requests.get, because you import requests directly (see where to patch).

UPDATE:
If you need the response to depend on the url, as shown in the updated question, you could use side_effect, which can take a function or class object (unless return_value, which needs an evaluated value):

class MockedResponse:
    def __init__(self, url):
        self.url = url

    @property
    def text(self):
        responses = {"url1": "response1",
                     "url2": "response2",
                     "url3": "response3"}
        if self.url in responses:
            return responses[self.url]
        return "default"


class TestFoo:
    def test_foo(self, mocker):
        a = Foo()
        get_mock = mocker.patch('requests.get')
        get_mock.side_effect = MockedResponse
        response = a.fooMethod("url2")
        assert response.text == "response2"
        assert get_mock.call_count == 1
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Thanks for the response - your answer does work, but I only gave you part of the story. I want to be able to return different values based on the url called. So I was following this example here: [link]https://stackoverflow.com/questions/15753390/how-can-i-mock-requests-and-the-response Is there any way I can achieve this please? – engeeaitch Feb 26 '21 at 14:58
  • It would help if you could edit the question accordingly. Is the URL an argument of `fooMethod`? – MrBean Bremen Feb 26 '21 at 15:05
  • I've updated the question. Thanks for your reply - I think I'm nearly there now, but I am getting an error: Fail: TypeError: MockedResponse() takes no arguments. Any ideas what I'm doing wrong? – engeeaitch Feb 26 '21 at 15:54
  • Apologies - I had`__init_` instead of `__init__` Many thanks for your help. – engeeaitch Feb 26 '21 at 16:13