How to mock a class instance (not a class function) in Python - python-3.x

I am having a hard time mocking an instance of an object.
I would like to write a unit test to test 'my_func' function that uses an instance of a class. I know how to mock class functions, however, I do not know how to mock an instance of the class (object) itself (not a function).
Inside my module file:
# my_module.py
import fancypackage1
import fancypackage2
def my_func():
x = fancypackage1.SomeClass.somefunction() # I know how to mock this
myclient = fancypackage2.Client() # I don't know how to mock this
myresult = do_something(myclient, x) # I know how to mock this
return myresult
Inside my test file:
# test_my_module.py
import pytest
import mock
import fancypackage1
import fancypackage2
from my_module import my_func
def test_my_func(mocker):
mock_someclass_somefunction = mocker.patch('my_module.fancypackage1.SomeClass.somefunction')
mock_someclass_somefunction.return_value = 'hello'
mock_client = mocker.patch.object(fancypackage2.Client, '__init__') # TypeError: __init__() should return None, not 'MagicMock'
mock_do_something = mocker.patch('my_module.do_something')
my_func()
mock_do_something.assert_called_with(mock_client, 'hello')
Since I did not know how to mock an instance of a class, but I knew how to mock a class method, I figured that perhaps, for the instance of the class, using the constructor function might work - and so I used init, but this did not work for me unfortunately, I am getting an error:
E TypeError: __init__() should return None, not 'MagicMock'
After the above did not work, I tried passing a custom-made fixture:
#pytest.fixture
def client_constructor_mock():
my_client = fancypackage2.Client()
return my_client
def test_my_func(mocker, client_constructor_mock):
mock_someclass_somefunction = mocker.patch('my_module.fancypackage1.SomeClass.somefunction')
mock_someclass_somefunction.return_value = 'hello'
mock_client = client_constructor_mock
mock_do_something = mocker.patch('my_module.do_something')
my_func()
mock_do_something.assert_called_with(mock_client, 'hello')
Unfortunately, this did not work either. The error I am getting:
> mock_do_something.assert_called_with(mock_client, 'hello')
E AssertionError: Expected call: do_something(<fancypackage2.Client object at 0x000001E6896A69C8>, 'hello')
E Actual call: do_something(<fancypackage2.Client object at 0x000001E689721488>, 'hello')
which tells me that there are two different objects of class Client, and that's the error.
I am at a loss here, how do I ensure that myclient is mocked correctly? Any help is very much appreciated.

__init__ cannot be patched directly for manipulating the instances created by the class, as the TypeError suggests.
This can be done by patching the class and requesting the return_value of that mock-object, which is the result of calling the __init__ of that class.
Instead of
mock_client = mocker.patch.object(fancypackage2.Client, '__init__') # TypeError: __init__() should return None, not 'MagicMock'
The following should work:
mock_client_class = mocker.patch('my_module.fancypackage2.Client')
mock_client = mock_client_class.return_value

Related

python mock - patching a method with itself - missing 1 required positional argument

I would like to patch a method to able to test whether it was called or not, but in the same time I dont want to loose the functionality so the idea was to patch the method with itself:
import unittest
from unittest.mock import Mock, patch
class MyClass():
def foo(self, num):
return num + 2
class myTestClass(unittest.TestCase):
#patch.object(MyClass,'foo', Mock(wraps=MyClass.foo))
def test_foo(self):
my_class = MyClass()
result = my_class.foo(2)
my_class.foo.assert_called_once_with(2)
self.assertEqual(result, 4)
During execution I am getting the following error:
File "/usr/lib64/python3.6/unittest/mock.py", line 1014, in _mock_call
return self._mock_wraps(*args, **kwargs)
TypeError: foo() missing 1 required positional argument: 'num'
Is this kind of patching possible in this way?
There is probably a workaround described here
I see several problems here:
You are patching the class, not the object; but testing the object, not the class.
You are wrapping with a Mock which I feel that it is wrong.
And a minor one: you have named the object that you want to test as my_class, which can be misread easily.
I think that a right solution for your algorithm can be:
import unittest
from unittest.mock import Mock, patch
class MyClass():
def foo(self, num):
return num + 2
class myTestClass(unittest.TestCase):
#patch.object(MyClass,'foo', wraps=MyClass.foo)
def test_foo(self, mocked):
obj = MyClass()
result = MyClass.foo(obj, 2)
MyClass.foo.assert_called_once_with(obj, 2)
self.assertEqual(result, 4)
if __name__ == '__main__':
unittest.main()
Note that:
wraps=MyClass.foo instead of wraps=Mock(...).
def test_do(self, mocked) instead of def test_do(self) (mock object is passed to the method).
MyClass.foo(obj, 2) since you have been patched the class, not the object.
MyClass.foo.assert_called_once_with(obj, 2) since you must check the class method (which you have been patched).
Alternatively, you can patch the object like in this test:
def test_foo_object(self):
obj = MyClass()
with patch.object(obj, 'foo', wraps=obj.foo):
result = obj.foo(2)
obj.foo.assert_called_once_with(2)
self.assertEqual(result, 4)

What is the type of function inside a class by default if no args are passed?

class A:
#classmethod
def a(x):
print('a')
#staticmethod
def b():
print('b')
def c():
print('c')
def d(self): #instance method
print(self)
A.a() # calling using class
A.b() # calling using class
A().b() # calling using instance
A.c() # calling using class
A().d() # calling using the object
A().c() # calling using instance but returning error
I've created a simple class and different methods, what is the type of method c( ) here? it is not static as if it was static method it could have been called by an instance too but it returns an error in that case, it is not an class method either as if it was, we should have get argument like cls while defining the function, so what type is the method c( ) here?
You need #staticmethod in Python 3 if you want to call the method on an instance too. If you don't use the decorator, the method will always be passed the instance as the first parameter, causing a TypeError.
Here is an example:
>>> class A:
>>> def f():
>>> print('hello')
>>> A.f()
hello
>>> A().f()
TypeError: f() takes 0 positional arguments but 1 was given
>>> class A:
>>> #staticmethod
>>> def f():
>>> print('hello')
>>> A.f()
hello
>>> A().f()
hello
In Python 3 there is no difference between a function defined inside a class or a function defined outside a class. Both of them are normal functions.
The self parameter (or maybe cls) comes into picture only when you access the function through an instance. Hence you don't get any error if you don't use an instance.

How to use a pytest fixture to instantiate a object under test?

It appears that fixtures should be used to instantiate an object under test for pytest, especially if it is used by several test_ functions. However, after trying to adapt examples given in the pytest doc, I cannot get the following to work.
import pytest
...
#pytest.fixture
def newMyClass():
obj = MyClass(1,2)
...
def test_aMethod(newMyClass):
objectUnderTest = newMyClass.obj
...
There are no complaints about the fixture or the constructor, but then I receive the pytest error
def test_aMethod(newMyClass):
> objectUnderTest = newMyClass.obj()
E AttributeError: 'NoneType' object has no attribute 'obj'
If fixtures can be used for this, how should that be coded?
To clean up #hoefling's answer, you need to instantiate your class directly and return that instance. Check out this code if you're looking for a cleaned up version.
import pytest
class MyClass():
def __init__(self, obj, foo):
self.obj = obj
self.foo = foo
#pytest.fixture
def newMyClass():
myClassInstance = MyClass(1,2)
return myClassInstance
def test_aMethod(newMyClass):
objectUnderTest = newMyClass.obj
assert objectUnderTest

Mocking a class in a Flask API

I have three files
helper.py
class helper:
def __init__(self, out_file):
self.out_file = out_file
def foo(first, second):
# Write data to file
flask_API.py
from helper import helper
#app.route('/', methods=['POST'])
def parse_request():
content = request.get_json()
out_file = #based on timestamp
helper(out_file).foo(content['first'], content['second'])
test_flask.py
import unittest
from unittest.mock import patch
import flask_API
class testFlaskAPI(unittest.TestCase):
def setUp(self):
self.app = flask_API.app.test_client()
self.app.test = True
#patch('flask_API.app.helper', return_value=None)
def test_service(self, mock_helper):
response = self.app.post(base_url, data=json.dumps({"some":"value"}, content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
I am having trouble mocking the helper class. This gives me an error saying
AttributeError: <Flask 'flask_API'> does not have the attribute 'helper'
I read that a class/method needs to be mocked where it is being called instead of where it's defined. Is there something wrong with the way I am patching the class?
In the end the solution turned out to be fairly simple. First there was no need to add app in the #patch decorator. The test just needed #patch('flask_API.helper'). Second, I first needed to return the mock of the class and then mock the function call as well. So the final answer turned out to be
#patch('flask_API.helper')
def test_service(self, mock_helper):
mocking_helper = mock_helper.return_value # mocking the class
mocking_helper.foo.return_value = None
response = self.app.post(base_url, data=json.dumps({"some":"value"}, content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

class instance from nowhere [duplicate]

If I have a class ...
class MyClass:
def method(arg):
print(arg)
... which I use to create an object ...
my_object = MyClass()
... on which I call method("foo") like so ...
>>> my_object.method("foo")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method() takes exactly 1 positional argument (2 given)
... why does Python tell me I gave it two arguments, when I only gave one?
In Python, this:
my_object.method("foo")
... is syntactic sugar, which the interpreter translates behind the scenes into:
MyClass.method(my_object, "foo")
... which, as you can see, does indeed have two arguments - it's just that the first one is implicit, from the point of view of the caller.
This is because most methods do some work with the object they're called on, so there needs to be some way for that object to be referred to inside the method. By convention, this first argument is called self inside the method definition:
class MyNewClass:
def method(self, arg):
print(self)
print(arg)
If you call method("foo") on an instance of MyNewClass, it works as expected:
>>> my_new_object = MyNewClass()
>>> my_new_object.method("foo")
<__main__.MyNewClass object at 0x29045d0>
foo
Occasionally (but not often), you really don't care about the object that your method is bound to, and in that circumstance, you can decorate the method with the builtin staticmethod() function to say so:
class MyOtherClass:
#staticmethod
def method(arg):
print(arg)
... in which case you don't need to add a self argument to the method definition, and it still works:
>>> my_other_object = MyOtherClass()
>>> my_other_object.method("foo")
foo
In simple words
In Python you should add self as the first parameter to all defined methods in classes:
class MyClass:
def method(self, arg):
print(arg)
Then you can use your method according to your intuition:
>>> my_object = MyClass()
>>> my_object.method("foo")
foo
For a better understanding, you can also read the answers to this question: What is the purpose of self?
Something else to consider when this type of error is encountered:
I was running into this error message and found this post helpful. Turns out in my case I had overridden an __init__() where there was object inheritance.
The inherited example is rather long, so I'll skip to a more simple example that doesn't use inheritance:
class MyBadInitClass:
def ___init__(self, name):
self.name = name
def name_foo(self, arg):
print(self)
print(arg)
print("My name is", self.name)
class MyNewClass:
def new_foo(self, arg):
print(self)
print(arg)
my_new_object = MyNewClass()
my_new_object.new_foo("NewFoo")
my_bad_init_object = MyBadInitClass(name="Test Name")
my_bad_init_object.name_foo("name foo")
Result is:
<__main__.MyNewClass object at 0x033C48D0>
NewFoo
Traceback (most recent call last):
File "C:/Users/Orange/PycharmProjects/Chapter9/bad_init_example.py", line 41, in <module>
my_bad_init_object = MyBadInitClass(name="Test Name")
TypeError: object() takes no parameters
PyCharm didn't catch this typo. Nor did Notepad++ (other editors/IDE's might).
Granted, this is a "takes no parameters" TypeError, it isn't much different than "got two" when expecting one, in terms of object initialization in Python.
Addressing the topic: An overloading initializer will be used if syntactically correct, but if not it will be ignored and the built-in used instead. The object won't expect/handle this and the error is thrown.
In the case of the sytax error: The fix is simple, just edit the custom init statement:
def __init__(self, name):
self.name = name
Newcomer to Python, I had this issue when I was using the Python's ** feature in a wrong way. Trying to call this definition from somewhere:
def create_properties_frame(self, parent, **kwargs):
using a call without a double star was causing the problem:
self.create_properties_frame(frame, kw_gsp)
TypeError: create_properties_frame() takes 2 positional arguments but 3 were given
The solution is to add ** to the argument:
self.create_properties_frame(frame, **kw_gsp)
As mentioned in other answers - when you use an instance method you need to pass self as the first argument - this is the source of the error.
With addition to that,it is important to understand that only instance methods take self as the first argument in order to refer to the instance.
In case the method is Static you don't pass self, but a cls argument instead (or class_).
Please see an example below.
class City:
country = "USA" # This is a class level attribute which will be shared across all instances (and not created PER instance)
def __init__(self, name, location, population):
self.name = name
self.location = location
self.population = population
# This is an instance method which takes self as the first argument to refer to the instance
def print_population(self, some_nice_sentence_prefix):
print(some_nice_sentence_prefix +" In " +self.name + " lives " +self.population + " people!")
# This is a static (class) method which is marked with the #classmethod attribute
# All class methods must take a class argument as first param. The convention is to name is "cls" but class_ is also ok
#classmethod
def change_country(cls, new_country):
cls.country = new_country
Some tests just to make things more clear:
# Populate objects
city1 = City("New York", "East", "18,804,000")
city2 = City("Los Angeles", "West", "10,118,800")
#1) Use the instance method: No need to pass "self" - it is passed as the city1 instance
city1.print_population("Did You Know?") # Prints: Did You Know? In New York lives 18,804,000 people!
#2.A) Use the static method in the object
city2.change_country("Canada")
#2.B) Will be reflected in all objects
print("city1.country=",city1.country) # Prints Canada
print("city2.country=",city2.country) # Prints Canada
It occurs when you don't specify the no of parameters the __init__() or any other method looking for.
For example:
class Dog:
def __init__(self):
print("IN INIT METHOD")
def __unicode__(self,):
print("IN UNICODE METHOD")
def __str__(self):
print("IN STR METHOD")
obj = Dog("JIMMY", 1, 2, 3, "WOOF")
When you run the above programme, it gives you an error like that:
TypeError: __init__() takes 1 positional argument but 6 were given
How we can get rid of this thing?
Just pass the parameters, what __init__() method looking for
class Dog:
def __init__(self, dogname, dob_d, dob_m, dob_y, dogSpeakText):
self.name_of_dog = dogname
self.date_of_birth = dob_d
self.month_of_birth = dob_m
self.year_of_birth = dob_y
self.sound_it_make = dogSpeakText
def __unicode__(self, ):
print("IN UNICODE METHOD")
def __str__(self):
print("IN STR METHOD")
obj = Dog("JIMMY", 1, 2, 3, "WOOF")
print(id(obj))
If you want to call method without creating object, you can change method to static method.
class MyClass:
#staticmethod
def method(arg):
print(arg)
MyClass.method("i am a static method")
I get this error when I'm sleep-deprived, and create a class using def instead of class:
def MyClass():
def __init__(self, x):
self.x = x
a = MyClass(3)
-> TypeError: MyClass() takes 0 positional arguments but 1 was given
You should actually create a class:
class accum:
def __init__(self):
self.acc = 0
def accumulator(self, var2add, end):
if not end:
self.acc+=var2add
return self.acc
In my case, I forgot to add the ()
I was calling the method like this
obj = className.myMethod
But it should be is like this
obj = className.myMethod()

Resources