I'm unit testing a class that uses peewee for ORM. I used mock for testing and tried to make sure I set the correct values for the model's attributes, but what I set for the model's constructor. When passing as an argument with a keyword, the mock.Mock class could not be tested as it is, so I created a mock that sets the argument with a keyword to the attribute.
It's only two months old in Python, and you may have made a big mistake, and there may be a smarter way to do it. If anyone knows any mistakes or better ways, it would be greatly appreciated if you could comment.
2016/10/15 postscript The code in this article only supports the case where only one instance of the mock is created. In the case of multiple instances, you need a method like @ podhmo's exemplified code in the comment.
$ pip install peewee
$ pip install mock
$ tree
.
├── src
│ ├── __init__.py
│ ├── model.py
│ └── service.py
└── test
├── __init__.py
└── test_service.py
src/service.py
# -*- coding: utf-8 -*-
from model import HogeModel
class HogeService(object):
def run(self):
model = HogeModel(name='hoge name')
model.desc = 'hoge desc'
model.save()
src/model.py
# -*- coding: utf-8 -*-
from peewee import SqliteDatabase
from peewee import Model
from peewee import PrimaryKeyField
from peewee import CharField
db = SqliteDatabase('hoge.db')
class HogeModel(Model):
id = PrimaryKeyField
name = CharField()
desc = CharField()
class Meta:
database = db
test/test_service.py
# -*- coding: utf-8 -*-
import unittest
from mock import patch
from mock import Mock
from service import HogeService
class TestHogeService(unittest.TestCase):
@patch('service.HogeModel')
def test_run(self, mock_hoge_model):
mock = Mock()
mock_hoge_model.return_value = mock
target = HogeService()
target.run()
self.assertEquals(1, mock.save.call_count) # OK
self.assertEquals('hoge desc', mock.desc) # OK
self.assertEquals('hoge name', mock.name) # NG
if __name__ == '__main__':
unittest.main()
$ PYTHONPATH=src python test/test_service.py
F
======================================================================
FAIL: test_run (__main__.TestHogeService)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/otti/.pyenv/versions/2.7.12/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "test/test_service.py", line 18, in test_run
self.assertEquals('hoge name', mock.name) # NG
AssertionError: 'hoge name' != <Mock name='HogeModel().name' id='4454023888'>
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
model = HogeModel (name ='hoge name')
in service.HogeService is not set in the mock attribute.test/test_service.py
# -*- coding: utf-8 -*-
import unittest
from mock import Mock
from mock import patch
from service import HogeService
class TestHogeService(unittest.TestCase):
def create_attr_set_mock(self, **kwargs):
self.mock = Mock()
#Set the keyword argument passed in the constructor to the attribute
for k, v in kwargs.items():
self.mock.__dict__[k] = v
return self.mock
@patch('service.HogeModel')
def test_run(self, mock_hoge_model):
#Returns a mock that sets the constructor's keyworded arguments to the attribute when instantiating the target class
mock_hoge_model.side_effect = lambda **kwargs: self.create_attr_set_mock(**kwargs)
target = HogeService()
target.run()
self.assertEquals(1, self.mock.save.call_count) # OK
self.assertEquals('hoge desc', self.mock.desc) # OK
self.assertEquals('hoge name', self.mock.name) # OK
if __name__ == '__main__':
unittest.main()
PYTHONPATH=src python test/test_service.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Now you have a mock of the model that works as expected. It was a bit confusing because mock.Mock seems to have an attribute called name in the first place, but it was the same when I tried it with attributes other than name.
Recommended Posts