Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ def string_containing_matcher():

Used like this:

>>> f = mock.Mock(return_value=None)
>>> f = mock.Mock()
>>> f("dogs are great")
>>> f.assert_any_call("dogs") # will raise AssertionError
Traceback (most recent call last):
Expand All @@ -359,6 +359,9 @@ def __eq__(self, test_string):
if not isinstance(test_string, str):
return False

if len(self.substring) > len(test_string):
return False

return self.substring in test_string

return StringContaining
Expand All @@ -374,7 +377,7 @@ def dictionary_containing_matcher():

Used like this:

>>> f = mock.Mock(return_value=None)
>>> f = mock.Mock()
>>> f({"dogs": "yes", "cats": "maybe"})
>>> f.assert_any_call({"dogs": "yes"}) # will raise AssertionError
Traceback (most recent call last):
Expand All @@ -391,6 +394,67 @@ 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)
if len(self.subdict) > len(test_dict):
return False

# Have to test self == other (rather than vice-versa) in case
# any of the values in self.subdict is another matcher with a custom
# __eq__ method (in LHS == RHS, LHS's __eq__ is tried before RHS's).
# In other words, this order is important so that examples like
# {"dogs": "are great"} == DictionaryContaining({"dogs": StringContaining("great")})
# evaluate to True
return all(self.subdict[key] == test_dict.get(key) for key in self.subdict)

return DictionaryContaining


@pytest.fixture(name="ObjectDescribedBy")
def object_described_by_matcher():
"""
An object which matches any other object with the given properties.

Available properties currently are "type" (a type object) and "attrs" (a
dictionary).

Useful for assert_called_with, assert_any_call, etc.

Used like this:

>>> class Dog(object):
... pass
...
>>> maisey = Dog()
>>> maisey.name = "Maisey"
>>> maisey.age = 7
>>> f = mock.Mock()
>>> f(maisey)
>>> f.assert_any_call(ObjectDescribedBy(type=Dog)) # no AssertionError
>>> f.assert_any_call(ObjectDescribedBy(attrs={"name": "Maisey"})) # no AssertionError
"""

class ObjectDescribedBy(object):
def __init__(self, type=None, attrs=None):
self.type = type
self.attrs = attrs

def __eq__(self, test_obj):
if self.type:
if not isinstance(test_obj, self.type):
return False

# all checks here done with getattr rather than comparing to
# __dict__ because __dict__ isn't guaranteed to exist
if self.attrs:
# attributes must exist AND values must match
try:
if any(
getattr(test_obj, attr_name) != attr_value
for attr_name, attr_value in self.attrs.items()
):
return False # wrong attribute value
except AttributeError: # missing attribute
return False

return True

return ObjectDescribedBy
110 changes: 110 additions & 0 deletions tests/test_conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import pytest


@pytest.mark.parametrize(
"test_string, expected_result",
[
# type matches
("dogs are great!", True), # full containment - beginning
("go, dogs, go!", True), # full containment - middle
("I like dogs", True), # full containment - end
("dogs", True), # equality
("", False), # reverse containment
("dog", False), # reverse containment
("good dog!", False), # partial overlap
("cats", False), # no overlap
# type mismatches
(1231, False),
(11.21, False),
([], False),
({}, False),
(True, False),
],
)
def test_string_containing(
test_string, expected_result, StringContaining # noqa: N803
):

assert (test_string == StringContaining("dogs")) is expected_result


@pytest.mark.parametrize(
"test_dict, expected_result",
[
# type matches
({"dogs": "yes", "cats": "maybe", "spiders": "nope"}, True), # full containment
({"dogs": "yes", "cats": "maybe"}, True), # equality
({}, False), # reverse containment
({"dogs": "yes"}, False), # reverse containment
({"dogs": "yes", "birds": "only outside"}, False), # partial overlap
({"coyotes": "from afar"}, False), # no overlap
# type mismatches
('{"dogs": "yes", "cats": "maybe"}', False),
(1231, False),
(11.21, False),
([], False),
(True, False),
],
)
def test_dictionary_containing(
test_dict, expected_result, DictionaryContaining # noqa: N803
):

assert (
test_dict == DictionaryContaining({"dogs": "yes", "cats": "maybe"})
) is expected_result


class Animal(object): # noqa: B903
def __init__(self, name=None, age=None, description=None):
self.name = name
self.age = age
self.description = description


class Dog(Animal):
pass


class Cat(Animal):
pass


@pytest.mark.parametrize(
"test_obj, type_and_attrs_result, type_only_result, attrs_only_result",
[
# type matches
(Dog("Maisey", 7, "silly"), True, True, True), # full attr containment
(Dog("Maisey", 7), True, True, True), # type and attr equality
(Dog(), False, True, False), # reverse attr containment
(Dog("Maisey"), False, True, False), # reverse attr containment
(Dog("Charlie", 7, "goofy"), False, True, False), # partial attr overlap
(Dog("Bodhi", 6, "floppy"), False, True, False), # no attr overlap
# type mismatches
(Cat("Maisey", 7), False, False, True), # attr equality
(Cat("Piper", 1, "doglike"), False, False, False),
("Good girl, Maisey", False, False, False),
({"name": "Maisey", "age": 7}, False, False, False),
(1231, False, False, False),
(11.21, False, False, False),
([], False, False, False),
(True, False, False, False),
],
)
def test_object_described_by(
test_obj,
type_and_attrs_result,
type_only_result,
attrs_only_result,
ObjectDescribedBy, # noqa: N803
):

assert (
test_obj == ObjectDescribedBy(type=Dog, attrs={"name": "Maisey", "age": 7})
) is type_and_attrs_result

assert (test_obj == ObjectDescribedBy(type=Dog)) is type_only_result

assert (
test_obj == ObjectDescribedBy(attrs={"name": "Maisey", "age": 7})
) is attrs_only_result