diff --git a/test-requirements.txt b/test-requirements.txt index bd518645e2..4112712ebb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,6 +6,7 @@ pytest-localserver==0.5.0 pytest-cov==2.8.1 jsonschema==3.2.0 pyrsistent==0.16.0 # TODO(py3): 0.17.0 requires python3, see https://github.com/tobgu/pyrsistent/issues/205 +mock # for testing under python < 3.3 gevent eventlet diff --git a/tests/conftest.py b/tests/conftest.py index d5589238b5..499bfc7cf0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import os import json +from types import FunctionType import pytest import jsonschema @@ -36,6 +37,11 @@ def benchmark(): else: del pytest_benchmark +try: + from unittest import mock # python 3.3 and above +except ImportError: + import mock # python < 3.3 + @pytest.fixture(autouse=True) def internal_exceptions(request, monkeypatch): @@ -327,3 +333,83 @@ def render_span(span): return "\n".join(render_span(root_span)) return inner + + +@pytest.fixture(name="StringContaining") +def string_containing_matcher(): + """ + An object which matches any string containing the substring passed to the + object at instantiation time. + + Useful for assert_called_with, assert_any_call, etc. + + Used like this: + + >>> f = mock.Mock(return_value=None) + >>> f("dogs are great") + >>> f.assert_any_call("dogs") # will raise AssertionError + Traceback (most recent call last): + ... + AssertionError: mock('dogs') call not found + >>> f.assert_any_call(StringContaining("dogs")) # no AssertionError + + """ + + class StringContaining(object): + def __init__(self, substring): + self.substring = substring + + def __eq__(self, test_string): + if not isinstance(test_string, str): + return False + + return self.substring in test_string + + return StringContaining + + +@pytest.fixture(name="DictionaryContaining") +def dictionary_containing_matcher(): + """ + An object which matches any dictionary containing all key-value pairs from + the dictionary passed to the object at instantiation time. + + Useful for assert_called_with, assert_any_call, etc. + + Used like this: + + >>> f = mock.Mock(return_value=None) + >>> f({"dogs": "yes", "cats": "maybe"}) + >>> f.assert_any_call({"dogs": "yes"}) # will raise AssertionError + Traceback (most recent call last): + ... + AssertionError: mock({'dogs': 'yes'}) call not found + >>> f.assert_any_call(DictionaryContaining({"dogs": "yes"})) # no AssertionError + """ + + class DictionaryContaining(object): + def __init__(self, subdict): + self.subdict = subdict + + def __eq__(self, test_dict): + if not isinstance(test_dict, dict): + return False + + return all(test_dict.get(key) == self.subdict[key] for key in self.subdict) + + return DictionaryContaining + + +@pytest.fixture(name="FunctionMock") +def function_mock(): + """ + Just like a mock.Mock object, but one which always passes an isfunction + test. + """ + + class FunctionMock(mock.Mock): + def __init__(self, *args, **kwargs): + super(FunctionMock, self).__init__(*args, **kwargs) + self.__class__ = FunctionType + + return FunctionMock diff --git a/tox.ini b/tox.ini index cb0008702f..a29ba612fd 100644 --- a/tox.ini +++ b/tox.ini @@ -83,6 +83,9 @@ envlist = [testenv] deps = + # if you change test-requirements.txt and your change is not being reflected + # in what's installed by tox (when running tox locally), try running tox + # with the -r flag -r test-requirements.txt django-{1.11,2.0,2.1,2.2,3.0,3.1,dev}: djangorestframework>=3.0.0,<4.0.0