12

I'm starting to learn Python, and along with that, I try learn how to write tests for my code. I've decided to use py.test and mock for that. I'm given a quite big and complex class, to write tests for, so for a start I decided to work on a simpler example of my own.

So, I've written a very simple class (person.py in a package called src_pkg)

class Person():


    def __init__(self, name, age):
        self.name = name
        self.age = age

    def can_do_it(self, x):
        result = True if x > 5 else False
        print "result: ", result
        return result

What I want to do, is mock the Person class, and create an instance of the mocked class, in order to be able to call the can_do_it() method.

The reason I want to do that, is because the real class I'm working on, has a really complex constructor, and I don't want to make an instance of the class by writing something like foo = Foo(x, y, z)

So, I've written my test code (test_person.py in a package called test_pkg), which is the following:

from mock import patch

class TestPerson():

    def test_can_do_it(self):
        with patch('src_pck.person.Person') as Person:
            person = Person.return_value
            print "can he do it? :", person.can_do_it(4)

but when I run:

$ py.test -v -s test_person.py

I get the following result:

platform linux2 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- /home/kostas/.virtualenvs/excite/bin/python
collected 1 items 

test_person.py:5: TestPerson.test_can_do_it Can he do it? : <MagicMock name='Person().can_do_it()' id='37709904'>
PASSED

I would expect that the expression print "can he do it? :", person.can_do_it(4) would result to can he do it? : False. As a result, there is no point of asserting anything.

I think that when I run the test, it does not call the can_do_it() method at all! (otherwise the print statement of the method would be printed, right?)

So, what am I doing wrong?

Any help would be really appreciated.

Thank you in advance.

kostasg
  • 133
  • 1
  • 8

2 Answers2

6

Patch the __init__ method using mock.patch.object:

from mock import patch
import src_pkg.person as p

class TestPerson():
    def test_can_do_it(self):
        with patch.object(p.Person, '__init__', lambda self: None):
            person = p.Person()
            print "can he do it? :", person.can_do_it(4)
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • 1
    It looks like the purpose of the test is to test the `can_do_it` method. If the return value is only needed to test some other code, setting the `return_value` is an appropriate solution. On the other hand, if `can_do_it` is the part under test, this doesn't test the method. – user2357112 Feb 14 '14 at 06:04
  • @falsetru: thank you for the answer. The thing is that, as user2357112 said, I don't want to mock the method call, and set it return a standard result. I want to test the actual method, so the above suggestion does not help me. And indeed there is a typo, but it is actually the other way around :) I'll try to fix that. Thanks anyway – kostasg Feb 14 '14 at 06:26
  • @kostasg, I misunderstood your question. I updated the answer. – falsetru Feb 14 '14 at 06:46
2

A mock object has mock methods, not the real methods. The real methods may depend on having a real, fully-constructed object of the right class as self, which a mock object can't provide. If you need to test the can_do_it method, you can't use a mock Person to do it.

If can_do_it doesn't depend on having a fully-constructed self available, you can move the implementation to a module-level function or static method and have the instance method call that:

class Person(object):
    ...
    def can_do_it(self, x):
        return _can_do_it(x)

def _can_do_it(x):
    result = True if x > 5 else False
    print "result: ", result
    return result

Then you can just test the module-level function. If you need certain bits and pieces of a Person, but you don't need to construct the whole thing, then you can just construct (or mock) the bits and pieces and have the module-level function take them as arguments.

If can_do_it depends on having a real self or most of one, you may need to construct a real Person object and call the method.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • thank you for the answer. The thing is, that actually the only part that the method is using from the `self`, is its `log` attribute. So either I'll remove this single log line contained in the method, or I'll have to create an actual `Person` object – kostasg Feb 14 '14 at 06:35
  • @kostasg: Depending on what the `log` depends on, you may be able to use a module-level function and pass it a real or mock `log`. (Answer expanded.) – user2357112 Feb 14 '14 at 06:38