Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

To Kill A Mocking Object: A Python Story

I have been having trouble with Python mock and have been going crazy. I've held off on this question due to fear of down voting for not enough research. I have a cumulative 24 hours over the last week trying to figure out how to get this work and cannot.

I have read numerous examples and have created this one from those. I know mock objects are supposed to be easy to use, but this has taken too long. Now I am out of time.

I am trying to do two simple things here:

1. Override a request.ok status code inside another function
2. Cause an urllib2.HTTPError exception to be thrown

I have distilled these two tasks into the simplest possible example for your convenience:

#ExampleModule.py

import requests
import urllib2

def hello_world():    
    try:
        print "BEGIN TRY"
        r = requests.request('GET', "http://127.0.0.1:80")
        print r.ok

        if r.ok:
            print "PATCH 1 FAILED"
        else:
            print "PATCH 1 SUCCESSFUL"

    except urllib2.HTTPError:
        print "PATCH 2 SUCCESSFUL"
        print "EXCEPTION 2 HIT\n"
    else:
        print "PATCH 2 FAILED\n"

and

#in TestModule.py

import mock
import ExampleModule     

def test_function_try():
    with mock.patch('ExampleModule.hello_world') as patched_request:
        patched_request.requests.request.ok = False
        result = ExampleModule.hello_world()
        print result

def test_function_exception():
    with mock.patch('ExampleModule.hello_world') as patched_exception:
        patched_exception.urllib2.side_effect = HTTPError
        result = ExampleModule.hello_world()
        print result

test_function_try()
test_function_exception()

A normal call to hello_world() outputs:

BEGIN TRY
True
<Response [200]>

A normal call to test_function_try() outputs:

<MagicMock name='hello_world()' id='70272816'>
#From the "print result" inside test_function_try()

A normal call to test_function_exception() outputs:

<MagicMock name='hello_world()' id='62320016'>
#From the "print result" inside test_function_exception()

Obviously, I am not actually returning anything from hello_world() so it looks like the patched object is the hello_world() function instead of the requests or urllib2 patched modules.

It should be noted that when I try to patch with 'ExampleModule.hello_world.requests' or 'ExampleModule.hello_world.urllib2' I get an error saying they cannot be found in hello_world()

QUESTION SUMMARY

  1. What is wrong with the two functions test_function_try() and test_function_exception()? What needs to be modified so that I can manually assign the value of request.ok inside hello_world() and also manually raise the exception HTTPError so that I can test the code in that block... Bonus points for explaining 'when' exactly the exception gets thrown: as soon as the try: is entered, or when request is called, or some other time?
  2. Something that has been a concern of mine: will my print statements inside the ExampleModule.py reveal whether my patching and mock tests are working or do I HAVE to use assert methods to get the truth? I am not sure whether assert is a necessity when people mention 'use assertions to find out if the actual patched object was called, etc.' or if this is for convenience/convention/practicality.

UPDATE

After changing the patch target to the requests.request() function, as per @chepner's suggestion, I receive the following output:

BEGIN TRY
False
PATCH 1 SUCCESSFUL
PATCH 2 FAILED

BEGIN TRY
Traceback (most recent call last):
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\TestModule.py", line 44, in <module>
    test_function_exception()
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\TestModule.py", line 19, in test_function_exception
    ExampleModule.hello_world()
File "C:\LOCAL\ECLIPSE PROJECTS\MockingTest\ExampleModule.py", line 12, in hello_world
    r = requests.request('GET', "http://127.0.0.1:8080")
File "C:\Python27\lib\site-packages\mock\mock.py", line 1062, in __call__
    return _mock_self._mock_call(*args, **kwargs)
File "C:\Python27\lib\site-packages\mock\mock.py", line 1118, in _mock_call
    raise effect
TypeError: __init__() takes exactly 6 arguments (1 given)
like image 703
user58446 Avatar asked Mar 21 '17 12:03

user58446


1 Answers

You can't mock local variables in a function. The thing you want to mock is requests.request itself, so that when hello_world calls it, a mock object is returned, rather than actually making an HTTP request.

import mock
import urllib2
import ExampleModule     

def test_function_try():
    with mock.patch('ExampleModule.requests.request') as patched_request:
        patched_request.return_value.ok = False
        ExampleModule.hello_world()

def test_function_exception():
    with mock.patch('ExampleModule.requests.request') as patched_request:
        patched_request.side_effect = urllib2.HTTPError
        ExampleModule.hello_world()

test_function_try()
test_function_exception()
like image 115
chepner Avatar answered Oct 08 '22 01:10

chepner