1

test_client/wclient.py

import json
import requests
client = requests.session()

def setup():
    response = REST_CLIENT.post(
        "https://placeholder.com",
        auth=(placeholder, placeholder),
        data={"grant_type": "client_credentials"},
    )

    status_code = response.status_code
    if status_code in OK_STATUS:
        payload = json.loads(response.content, object_pairs_hook=OrderedDict)
    else:
        payload = response.text
        msg = (
            "Status Code %s" % status_code
        )
        logger.error(msg)
        raise ValueError(msg)

    return payload["access_token"]

Test File: test_client/test_client.py

import mock
import wclient
@mock.patch("test_client.wclient")
def test_taxes_pitney_bowes_setup_success(resp):
    resp.return_value.post.return_value.status_code = "200"
    wclient.pitney_bowes_setup()

Status Code <MagicMock name='REST_CLIENT.post().status_code' id='4868492200'>

How can I mock the module's methods and attributes with mock.patch()? I've read through pages of stack overflow posts but I'm getting confused with all the different ways to enforce a magic mock.

I've tried mocking:

resp.return_value.post.return_value.status_code
resp.return_value.post.return_value.status_code.return_value
resp.post.return_value.status_code
resp.post.return_value.status_code.return_value
resp.post.status_code
resp.post.status_code.return_value
Rhubarb
  • 198
  • 1
  • 11

1 Answers1

2

I think there are numerous ways of actually doing the mock (see the many methods in Mocking Method Calls In Python). The way I like to do it and find easy for simple mocks is:

For functions: @patch('module.print', lambda x: None)

For attributes: @patch('module.cwd', os.path.join(os.getcwd(), "folder"))

This blog post might be helpful for you: https://medium.com/uckey/how-mock-patch-decorator-works-in-python-37acd8b78ae.

Let me know if you have more questions.

Edit: To add multiple mocks just add another attribute:

import wclient
@mock.patch("test_client.wclient")
@mock.patch("another_attribute", "value")
@mock.patch("another_function", lambda x, y: x + y)
def test_taxes_pitney_bowes_setup_success(resp):
    resp.return_value.post.return_value.status_code = "200"
    wclient.pitney_bowes_setup()

OrionTheHunter
  • 276
  • 1
  • 7
  • The blog posts helps my general understanding but I guess I don't really see how it helps the current problem at hand. `@patch('module.print', lambda x: None)` will set the value to a function, won't it? Edit: Accidentally hit the save edits. Continuing, what if I wanted to keep a generic `mock` object, but be able to have return values for multiple attributes, beyond just `status_code`? What if I wanted to do it for `status_code` and `text`? What if I wanted to be able to mock both posts and gets within the same method? – Rhubarb Aug 01 '19 at 22:54
  • 1
    Correct, it'll change the print function in the module to return None. You can use this to return any value and mock out a function, or use it to change a variable to a particular value (see the attribute mock). Edit: you can add multiple patches above your test to specify different attributes and function. You also can change that lambda to take in multiple arguments and return multiple arguments based on that input. I can add this example to the answer above. – OrionTheHunter Aug 01 '19 at 22:55
  • Are you saying that to mock both status_code and text for a post object, I would need to mock the post method? Or are you suggesting I need to mock the `status_code` and `text` attributes? – Rhubarb Aug 01 '19 at 22:57
  • To follow up with the new, edited post. In this scenario, the `MagicMock` object is `resp`. With multiple `mock.patch` decorators, what is effectively being mocked by `resp`? Is it `test_client.wclient`? How can one object account for more than one match? EDIT: I understand the `another_attribute` and `another_function` have their return values within the decorator. My mistake! But that doesn't fix the status code not being properly mocked. – Rhubarb Aug 01 '19 at 23:03
  • I don't think your solution answers my question. None of these suit the situation at hand. – Rhubarb Aug 01 '19 at 23:26
  • Perhaps I am not understanding the question correctly. Could you refine the question? I thought the question was how to mock a module's methods and attributes with mock.patch()? Are you are specifically looking to mock `status_code`? I'm not familiar with the `response` object, but from your code snippet it looks like you should mock `@mock.patch(".response.status_code", 200)` Edit: It looks like your module name is `test_client` – OrionTheHunter Aug 01 '19 at 23:38
  • I don't think you can patch response because that's not in the name space. It's a local variable within the function. I want to mock the return value of the `requests.session`, which has attributes. Effectively, mock the `requests.session` and the attributes of the return values of two of its methods, `GET` and `POST`. – Rhubarb Aug 01 '19 at 23:57
  • @Rhubarrbb Try and find what kind of object its methods return and patch that. E.g. if requests.session returns a Session instance from the requests library, then patch 'requests.Session' and its return values in your test. – Mathieu Dhondt Nov 30 '20 at 09:19