Skip to content

Instantly share code, notes, and snippets.

@magopian
Last active December 20, 2015 03:09
Show Gist options
  • Save magopian/6061233 to your computer and use it in GitHub Desktop.
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)
# -*- 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']
# -*- 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