Last active
December 20, 2015 03:09
-
-
Save magopian/6061233 to your computer and use it in GitHub Desktop.
fixtures for pytest, using monkeypatch, and providing stub facilities, also allowing to check the calls of the stubbed methods. For a fixture that makes use of the mock library, check pelme's https://gist.github.com/pelme/59a1dee00b5f8afc278e (example usage: https://gist.github.com/pelme/08571132a677485c7e23)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from __future__ import unicode_literals, absolute_import, division | |
import pytest | |
class Stub(object): | |
"""Stub methods, keep track of calls.""" | |
def __init__(self, monkeypatch): | |
self.monkeypatch = monkeypatch | |
self.stubbed = {} | |
def stub(self, obj, **kwargs): | |
"""Stub obj.method to return whatever parameter was passed for method. | |
Usage: stub(django.utils.timezone, now='now') | |
It's possible to stub several methods on the same object at once. | |
""" | |
self.stubbed[obj] = self.stubbed.get(obj, {}) | |
for method, val in kwargs.items(): | |
self._stub(obj, method, val) | |
return self.stubbed[obj] | |
def _stub(self, obj, method, value): | |
"""Wrap the value to be returned, to check for its calls.""" | |
def call(*args, **kwargs): | |
"""Return the value monkeypatched for this method, track call.""" | |
self.stubbed[obj][method].append({'args': args, 'kwargs': kwargs}) | |
return value(*args, **kwargs) | |
self.stubbed[obj][method] = [] # new stub: reset calls, if any | |
self.monkeypatch.setattr(obj, method, call) | |
@pytest.fixture(scope='function') | |
def stubber(monkeypatch): | |
"""Provides stubbing capabilities.""" | |
return Stub(monkeypatch) | |
@pytest.fixture(scope='function') | |
def stub_save(stubber): | |
"""Stub the Model.save() method to avoid database access.""" | |
from django.db.models import Model | |
def stubbed_save(self, *args, **kwargs): | |
return 'no database access' | |
stubbed = stubber.stub(Model, save=stubbed_save) | |
return stubbed['save'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from __future__ import unicode_literals, absolute_import, division | |
from django.db.models import Model | |
class StubVictim(object): | |
"""Used as a victim for the stubber.""" | |
def foo(self, *args, **kwargs): | |
return "bar" | |
def bar(self): | |
return "baz" | |
class TestStubber(object): | |
def test_init(self, monkeypatch, stubber): | |
"""Stubber is initialized with no stubbed method.""" | |
assert stubber.stubbed == {} | |
assert stubber.monkeypatch == monkeypatch | |
def test_stub_one(self, stubber): | |
"""Add one stubbed method to self.subbed.""" | |
assert StubVictim().foo() == 'bar' | |
stubber.stub(StubVictim, foo=lambda x: 'foo stubbed!') | |
assert stubber.stubbed[StubVictim] == {'foo': []} | |
assert StubVictim().foo() == 'foo stubbed!' | |
assert StubVictim().bar() == 'baz' | |
def test_stub_two(self, stubber): | |
"""Add two stubbed methods to self.subbed.""" | |
stubber.stub(StubVictim, foo=lambda x: 'foo stubbed!') | |
stubber.stub(StubVictim, bar=lambda x: 'bar stubbed!') | |
assert stubber.stubbed[StubVictim] == {'foo': [], 'bar': []} | |
assert StubVictim().foo() == 'foo stubbed!' | |
assert StubVictim().bar() == 'bar stubbed!' | |
def test_stub_multiple(self, stubber): | |
"""Stub multiple methods on the same object.""" | |
stubber.stub(StubVictim, | |
foo=lambda x: 'foo stubbed!', | |
bar=lambda x: 'bar stubbed!') | |
assert stubber.stubbed[StubVictim] == {'foo': [], 'bar': []} | |
assert StubVictim().foo() == 'foo stubbed!' | |
assert StubVictim().bar() == 'bar stubbed!' | |
def test_stub_track_calls(self, stubber): | |
"""Stub multiple methods on the same object.""" | |
victim = StubVictim() | |
stubbed_victim = stubber.stub(StubVictim, foo=lambda x: 'foo stubbed!') | |
victim.foo() | |
assert stubbed_victim['foo'] == [{'args': (victim,), 'kwargs': {}}] | |
victim.foo() | |
assert stubbed_victim['foo'] == [{'args': (victim,), 'kwargs': {}}, | |
{'args': (victim,), 'kwargs': {}}] | |
stubbed_victim = stubber.stub(StubVictim, | |
foo=lambda x, y, bar: 'foo stubbed!') | |
victim.foo('foo', bar='baz') | |
assert stubbed_victim['foo'] == [{'args': (victim, 'foo'), | |
'kwargs': {'bar': 'baz'}}] | |
class MyModel(Model): | |
"""Test model.""" | |
class TestStubSave(object): | |
def test_stub_save(self, stub_save): | |
"""stub_save prevents database access when calling save on a model.""" | |
# stub out get_unique_slug which otherwise would access the db | |
assert stub_save == [] # no call to Model.save() yet | |
mymodel = MyModel() | |
# would be prevented by pytest as this test function isn't marked as | |
# pytest.mark.django_db | |
mymodel.save() | |
assert stub_save == [{'args': (mymodel,), 'kwargs': {}}] | |
def test_stub_save_params(self, stub_save): | |
"""stub_save also accepts params like 'using'.""" | |
assert stub_save == [] # no call to Model.save() yet | |
mymodel = MyModel() | |
# would be prevented by pytest as this test function isn't marked as | |
# pytest.mark.django_db | |
mymodel.save(using='some database') | |
assert stub_save == [{'args': (mymodel,), | |
'kwargs': {'using': 'some database'}}] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment