From 83f47a9ba7b681a5d3c81c803f0234d1f277cdc7 Mon Sep 17 00:00:00 2001 From: MartinGotelli Date: Sun, 25 Aug 2024 15:20:43 -0300 Subject: [PATCH] fix: Add end start and contains support for matchers. Fix import order. Add not_empty_string and extra main matchers --- .../pytest_matchers/__init__.py | 6 ++ src/pytest_matchers/pytest_matchers/main.py | 28 +++++++ .../pytest_matchers/matchers/__init__.py | 2 +- .../pytest_matchers/matchers/between.py | 18 +++++ .../pytest_matchers/matchers/contains.py | 42 ++++++++-- .../pytest_matchers/matchers/datetime.py | 4 +- .../matchers/datetime_string.py | 3 +- .../pytest_matchers/matchers/ends_with.py | 27 ++++++- .../pytest_matchers/matchers/is_instance.py | 6 ++ .../pytest_matchers/matchers/length.py | 10 +++ .../pytest_matchers/matchers/list.py | 4 +- .../pytest_matchers/matchers/number.py | 4 +- .../pytest_matchers/matchers/starts_with.py | 27 ++++++- .../pytest_matchers/matchers/string.py | 8 +- .../pytest_matchers/matchers/uuid.py | 2 +- .../pydantic/matchers/pydantic_model.py | 6 +- .../pytest_matchers/utils/matcher_utils.py | 63 +-------------- .../pytest_matchers/utils/repr_utils.py | 5 ++ src/tests/examples/test_example.py | 15 ++++ src/tests/matchers/test_between.py | 15 ++++ src/tests/matchers/test_contains.py | 79 ++++++++++++++++++- src/tests/matchers/test_ends_with.py | 49 +++++++++++- src/tests/matchers/test_is_instance.py | 9 +++ src/tests/matchers/test_is_string.py | 11 +-- src/tests/matchers/test_length.py | 12 +++ src/tests/matchers/test_starts_with.py | 47 ++++++++++- src/tests/test_main.py | 69 +++++++++++++++- src/tests/utils/test_matcher_utils.py | 69 ---------------- 28 files changed, 473 insertions(+), 167 deletions(-) diff --git a/src/pytest_matchers/pytest_matchers/__init__.py b/src/pytest_matchers/pytest_matchers/__init__.py index 3edb0a1..6824bae 100644 --- a/src/pytest_matchers/pytest_matchers/__init__.py +++ b/src/pytest_matchers/pytest_matchers/__init__.py @@ -11,13 +11,19 @@ is_datetime, is_datetime_string, is_dict, + is_float, is_instance, + is_int, + is_iso_8601_date, + is_iso_8601_datetime, + is_iso_8601_time, is_json, is_list, is_number, is_strict_dict, is_string, is_uuid, + not_empty_string, one_of, same_value, ) diff --git a/src/pytest_matchers/pytest_matchers/main.py b/src/pytest_matchers/pytest_matchers/main.py index 15c81f5..4f27351 100644 --- a/src/pytest_matchers/pytest_matchers/main.py +++ b/src/pytest_matchers/pytest_matchers/main.py @@ -43,10 +43,26 @@ def is_string(**kwargs) -> String: return String(**kwargs) +def not_empty_string(**kwargs) -> String: + min_length = 1 + if "min_length" in kwargs: + min_length = kwargs.pop("min_length") + + return is_string(min_length=min_length, **kwargs) + + def is_number(match_type: Type = None, **kwargs) -> Number: return Number(match_type, **kwargs) +def is_int(**kwargs) -> Number: + return is_number(int, **kwargs) + + +def is_float(**kwargs) -> Number: + return is_number(float, **kwargs) + + def one_of(*values: Matcher | Any) -> Or: return Or(*values) @@ -92,6 +108,18 @@ def is_datetime_string( return DatetimeString(expected_format, min_value=min_value, max_value=max_value) +def is_iso_8601_date(**kwargs) -> DatetimeString: + return is_datetime_string("%Y-%m-%d", **kwargs) + + +def is_iso_8601_datetime(**kwargs) -> DatetimeString: + return is_datetime_string("%Y-%m-%dT%H:%M:%S", **kwargs) + + +def is_iso_8601_time(**kwargs) -> DatetimeString: + return is_datetime_string("%H:%M:%S", **kwargs) + + def same_value() -> SameValue: return SameValue() diff --git a/src/pytest_matchers/pytest_matchers/matchers/__init__.py b/src/pytest_matchers/pytest_matchers/matchers/__init__.py index 7400db8..b88aa4b 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/__init__.py +++ b/src/pytest_matchers/pytest_matchers/matchers/__init__.py @@ -1,10 +1,10 @@ from .base import Matcher from .matcher_factory import MatcherFactory +from .eq import Eq from .length import Length from .contains import Contains from .starts_with import StartsWith from .ends_with import EndsWith -from .eq import Eq from .between import Between from .and_matcher import And diff --git a/src/pytest_matchers/pytest_matchers/matchers/between.py b/src/pytest_matchers/pytest_matchers/matchers/between.py index b4ea659..e638d6d 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/between.py +++ b/src/pytest_matchers/pytest_matchers/matchers/between.py @@ -84,3 +84,21 @@ def _max_repr(self): return ( f"lower or equal than {self._max}" if self._max_inclusive else f"lower than {self._max}" ) + + +def between_matcher( + min_value: float | None, + max_value: float | None, + inclusive: bool | None, + min_inclusive: bool | None, + max_inclusive: bool | None, +) -> Between | None: + if min_value is None and max_value is None: + return None + return Between( + min_value, + max_value, + inclusive=inclusive, + min_inclusive=min_inclusive, + max_inclusive=max_inclusive, + ) diff --git a/src/pytest_matchers/pytest_matchers/matchers/contains.py b/src/pytest_matchers/pytest_matchers/matchers/contains.py index 2951c82..bb065e6 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/contains.py +++ b/src/pytest_matchers/pytest_matchers/matchers/contains.py @@ -2,22 +2,54 @@ from pytest_matchers.matchers import Matcher from pytest_matchers.matchers.matcher_factory import matcher +from pytest_matchers.utils.matcher_detector import MatcherDetector +from pytest_matchers.utils.repr_utils import as_matcher_repr + + +def _in_string_matcher(contained_value: Matcher, value: str): + if contained_value == value: + return True + for i in range(len(value)): + for j in range(i + 1, len(value) + 1): + substring = value[i:j] + if contained_value == substring: + return True + return False @matcher class Contains(Matcher): - def __init__(self, contained_value: Any): + def __init__(self, *contained_values: Any): super().__init__() - self._contained_value = contained_value + if len(contained_values) == 0: + raise ValueError("At least one value must be provided") + self._contained_values = contained_values def matches(self, value: Any) -> bool: try: - return self._contained_value in value + return all( + self._in(contained_value, value) for contained_value in self._contained_values + ) except TypeError: return False + @staticmethod + def _in(contained_value: Any, value: Any) -> bool: + if MatcherDetector(contained_value).uses_matchers() and isinstance(value, str): + return _in_string_matcher(contained_value, value) + return contained_value in value + def __repr__(self) -> str: - return f"To contain {repr(self._contained_value)}" + return f"To contain {', '.join(map(repr, self._contained_values))}" def concatenated_repr(self) -> str: - return f"containing {repr(self._contained_value)}" + return ( + "containing something expected " + f"{', and something '.join(map(as_matcher_repr, self._contained_values))}" + ) + + +def contains_matcher(contains: str | None) -> Contains | None: + if contains is None: + return None + return Contains(contains) diff --git a/src/pytest_matchers/pytest_matchers/matchers/datetime.py b/src/pytest_matchers/pytest_matchers/matchers/datetime.py index 7c9db75..a61a579 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/datetime.py +++ b/src/pytest_matchers/pytest_matchers/matchers/datetime.py @@ -4,10 +4,10 @@ from pytest_matchers.matchers.has_attribute import has_attribute_matcher from pytest_matchers.matchers.matcher_factory import matcher from pytest_matchers.utils.matcher_utils import ( - between_matcher, - is_instance_matcher, matches_or_none, ) +from pytest_matchers.matchers.is_instance import is_instance_matcher +from pytest_matchers.matchers.between import between_matcher from pytest_matchers.utils.repr_utils import concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/matchers/datetime_string.py b/src/pytest_matchers/pytest_matchers/matchers/datetime_string.py index 8ffbf9d..5f73f4e 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/datetime_string.py +++ b/src/pytest_matchers/pytest_matchers/matchers/datetime_string.py @@ -2,7 +2,8 @@ from pytest_matchers.matchers import String, Matcher from pytest_matchers.matchers.matcher_factory import matcher -from pytest_matchers.utils.matcher_utils import between_matcher, matches_or_none +from pytest_matchers.utils.matcher_utils import matches_or_none +from pytest_matchers.matchers.between import between_matcher from pytest_matchers.utils.repr_utils import concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/matchers/ends_with.py b/src/pytest_matchers/pytest_matchers/matchers/ends_with.py index be511eb..2b38260 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/ends_with.py +++ b/src/pytest_matchers/pytest_matchers/matchers/ends_with.py @@ -2,15 +2,32 @@ from pytest_matchers.matchers import Matcher from pytest_matchers.matchers.matcher_factory import matcher +from pytest_matchers.utils.matcher_detector import MatcherDetector +from pytest_matchers.utils.repr_utils import as_matcher_repr + + +def _ends_with_matcher(prefix: Matcher, value: Any): + if prefix == value: # With some luck the matcher matches with the whole value + return True + try: + for index in range(1, len(value)): + if prefix == value[-index:]: + return True + except TypeError: + pass + return False @matcher class EndsWith(Matcher): - def __init__(self, suffix: Sized | str): + def __init__(self, suffix: Sized | str | Matcher): super().__init__() self._suffix = suffix def matches(self, value: Any) -> bool: + if MatcherDetector(self._suffix).uses_matchers(): + return _ends_with_matcher(self._suffix, value) + if isinstance(value, str): try: return value.endswith(self._suffix) @@ -26,4 +43,10 @@ def __repr__(self) -> str: return f"To end with {repr(self._suffix)}" def concatenated_repr(self) -> str: - return f"ending with {repr(self._suffix)}" + return f"with ending expected {as_matcher_repr(self._suffix)}" + + +def ends_with_matcher(ends_with: str | None) -> EndsWith | None: + if ends_with is None: + return None + return EndsWith(ends_with) diff --git a/src/pytest_matchers/pytest_matchers/matchers/is_instance.py b/src/pytest_matchers/pytest_matchers/matchers/is_instance.py index 32e5f5e..b704333 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/is_instance.py +++ b/src/pytest_matchers/pytest_matchers/matchers/is_instance.py @@ -18,3 +18,9 @@ def __repr__(self) -> str: def concatenated_repr(self) -> str: return f"of '{self._match_type.__name__}' instance" + + +def is_instance_matcher(match_type: type | None) -> IsInstance | None: + if match_type is None: + return None + return IsInstance(match_type) diff --git a/src/pytest_matchers/pytest_matchers/matchers/length.py b/src/pytest_matchers/pytest_matchers/matchers/length.py index bbfa90d..4a7aaab 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/length.py +++ b/src/pytest_matchers/pytest_matchers/matchers/length.py @@ -47,3 +47,13 @@ def __repr__(self) -> str: if length_repr: return f"To have length {length_repr}" return length_repr + + +def length_matcher( + length: int | None, + min_length: int | None, + max_length: int | None, +) -> Length | None: + if length is None and max_length is None and min_length is None: + return None + return Length(length, min_length, max_length) diff --git a/src/pytest_matchers/pytest_matchers/matchers/list.py b/src/pytest_matchers/pytest_matchers/matchers/list.py index 58123ba..78894ae 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/list.py +++ b/src/pytest_matchers/pytest_matchers/matchers/list.py @@ -3,11 +3,11 @@ from pytest_matchers.matchers import IsInstance, Matcher from pytest_matchers.matchers.matcher_factory import matcher from pytest_matchers.utils.matcher_utils import ( - is_instance_matcher, - length_matcher, matches_or_none, partial_matches_or_none, ) +from pytest_matchers.matchers.is_instance import is_instance_matcher +from pytest_matchers.matchers.length import length_matcher from pytest_matchers.utils.repr_utils import concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/matchers/number.py b/src/pytest_matchers/pytest_matchers/matchers/number.py index 8603d73..5f5dda7 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/number.py +++ b/src/pytest_matchers/pytest_matchers/matchers/number.py @@ -3,10 +3,10 @@ from pytest_matchers.matchers import Matcher from pytest_matchers.matchers.matcher_factory import matcher from pytest_matchers.utils.matcher_utils import ( - between_matcher, - is_instance_matcher, matches_or_none, ) +from pytest_matchers.matchers.is_instance import is_instance_matcher +from pytest_matchers.matchers.between import between_matcher from pytest_matchers.utils.repr_utils import concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/matchers/starts_with.py b/src/pytest_matchers/pytest_matchers/matchers/starts_with.py index 6de51cc..946a47f 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/starts_with.py +++ b/src/pytest_matchers/pytest_matchers/matchers/starts_with.py @@ -2,15 +2,32 @@ from pytest_matchers.matchers import Matcher from pytest_matchers.matchers.matcher_factory import matcher +from pytest_matchers.utils.matcher_detector import MatcherDetector +from pytest_matchers.utils.repr_utils import as_matcher_repr + + +def _starts_with_matcher(prefix: Matcher, value: Any): + if prefix == value: # With some luck the matcher matches with the whole value + return True + try: + for index in range(1, len(value)): + if prefix == value[0:index]: + return True + except TypeError: + pass + return False @matcher class StartsWith(Matcher): - def __init__(self, prefix: Sized | str): + def __init__(self, prefix: Sized | str | Matcher): super().__init__() self._prefix = prefix def matches(self, value: Any) -> bool: + if MatcherDetector(self._prefix).uses_matchers(): + return _starts_with_matcher(self._prefix, value) + if isinstance(value, str): try: return value.startswith(self._prefix) @@ -25,4 +42,10 @@ def __repr__(self) -> str: return f"To start with {repr(self._prefix)}" def concatenated_repr(self) -> str: - return f"starting with {repr(self._prefix)}" + return f"with start expected {as_matcher_repr(self._prefix)}" + + +def starts_with_matcher(starts_with: str | None) -> StartsWith | None: + if starts_with is None: + return None + return StartsWith(starts_with) diff --git a/src/pytest_matchers/pytest_matchers/matchers/string.py b/src/pytest_matchers/pytest_matchers/matchers/string.py index a4462ce..f1e7650 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/string.py +++ b/src/pytest_matchers/pytest_matchers/matchers/string.py @@ -3,12 +3,12 @@ from pytest_matchers.matchers import IsInstance, Matcher from pytest_matchers.matchers.matcher_factory import matcher from pytest_matchers.utils.matcher_utils import ( - contains_matcher, - ends_with_matcher, - length_matcher, matches_or_none, - starts_with_matcher, ) +from pytest_matchers.matchers.contains import contains_matcher +from pytest_matchers.matchers.length import length_matcher +from pytest_matchers.matchers.starts_with import starts_with_matcher +from pytest_matchers.matchers.ends_with import ends_with_matcher from pytest_matchers.utils.repr_utils import concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/matchers/uuid.py b/src/pytest_matchers/pytest_matchers/matchers/uuid.py index fe1c14a..d36e62f 100644 --- a/src/pytest_matchers/pytest_matchers/matchers/uuid.py +++ b/src/pytest_matchers/pytest_matchers/matchers/uuid.py @@ -5,9 +5,9 @@ from pytest_matchers.matchers.matcher_factory import matcher from pytest_matchers.utils.matcher_utils import ( as_matcher_or_none, - is_instance_matcher, matches_or_none, ) +from pytest_matchers.matchers.is_instance import is_instance_matcher from pytest_matchers.utils.repr_utils import concat_matcher_repr, concat_reprs diff --git a/src/pytest_matchers/pytest_matchers/pydantic/matchers/pydantic_model.py b/src/pytest_matchers/pytest_matchers/pydantic/matchers/pydantic_model.py index 81d02b4..580d121 100644 --- a/src/pytest_matchers/pytest_matchers/pydantic/matchers/pydantic_model.py +++ b/src/pytest_matchers/pytest_matchers/pydantic/matchers/pydantic_model.py @@ -6,7 +6,8 @@ from pydantic.v1.fields import ModelField from pytest_matchers.matchers import HasAttribute, Matcher -from pytest_matchers.utils.matcher_utils import is_instance_matcher +from pytest_matchers.matchers.matcher_factory import matcher +from pytest_matchers.matchers.is_instance import is_instance_matcher from pytest_matchers.utils.repr_utils import concat_reprs @@ -39,6 +40,7 @@ def _version(model_class: Type) -> str | None: return None +@matcher class PydanticModel(Matcher): def __init__( self, @@ -95,7 +97,7 @@ def _attribute_matchers(self, value: Any = None) -> list[Matcher]: def matches(self, value: Any) -> bool: return self._matches_instance(value) and all( - matcher == value for matcher in self._attribute_matchers(value) + attr_matcher == value for attr_matcher in self._attribute_matchers(value) ) def _matches_instance(self, value: Any) -> bool: diff --git a/src/pytest_matchers/pytest_matchers/utils/matcher_utils.py b/src/pytest_matchers/pytest_matchers/utils/matcher_utils.py index 802a7be..e2bf4c7 100644 --- a/src/pytest_matchers/pytest_matchers/utils/matcher_utils.py +++ b/src/pytest_matchers/pytest_matchers/utils/matcher_utils.py @@ -1,15 +1,6 @@ from typing import Any, Callable -from pytest_matchers.matchers import ( - Between, - Contains, - EndsWith, - Eq, - IsInstance, - Length, - Matcher, - StartsWith, -) +from pytest_matchers.matchers import Eq, Matcher def as_matcher(value: Matcher | Any) -> Matcher: @@ -33,55 +24,3 @@ def _partial_matches_or_none(value: Any) -> bool: return matches_or_none(matcher, value) return _partial_matches_or_none - - -def is_instance_matcher(match_type: type | None) -> IsInstance | None: - if match_type is None: - return None - return IsInstance(match_type) - - -def contains_matcher(contains: str | None) -> Contains | None: - if contains is None: - return None - return Contains(contains) - - -def length_matcher( - length: int | None, - min_length: int | None, - max_length: int | None, -) -> Length | None: - if length is None and max_length is None and min_length is None: - return None - return Length(length, min_length, max_length) - - -def starts_with_matcher(starts_with: str | None) -> StartsWith | None: - if starts_with is None: - return None - return StartsWith(starts_with) - - -def ends_with_matcher(ends_with: str | None) -> EndsWith | None: - if ends_with is None: - return None - return EndsWith(ends_with) - - -def between_matcher( - min_value: float | None, - max_value: float | None, - inclusive: bool | None, - min_inclusive: bool | None, - max_inclusive: bool | None, -) -> Between | None: - if min_value is None and max_value is None: - return None - return Between( - min_value, - max_value, - inclusive=inclusive, - min_inclusive=min_inclusive, - max_inclusive=max_inclusive, - ) diff --git a/src/pytest_matchers/pytest_matchers/utils/repr_utils.py b/src/pytest_matchers/pytest_matchers/utils/repr_utils.py index fbbb024..b33f7cf 100644 --- a/src/pytest_matchers/pytest_matchers/utils/repr_utils.py +++ b/src/pytest_matchers/pytest_matchers/utils/repr_utils.py @@ -1,6 +1,7 @@ from typing import Any from pytest_matchers.matchers import Matcher +from pytest_matchers.utils.matcher_utils import as_matcher def _repr(extra_repr: Any | Matcher | None) -> str: @@ -13,6 +14,10 @@ def concat_matcher_repr(matcher: Matcher | None) -> str: return _repr(matcher) +def as_matcher_repr(value: Any | Matcher | None) -> str: + return concat_matcher_repr(as_matcher(value)) + + def concat_reprs( base_repr: str | Matcher | None, *extra_reprs: str | Matcher | None, diff --git a/src/tests/examples/test_example.py b/src/tests/examples/test_example.py index 31fafa4..e084acd 100644 --- a/src/tests/examples/test_example.py +++ b/src/tests/examples/test_example.py @@ -207,6 +207,21 @@ def _condition(value): ) +def test_is_string_with_created_datetime_matcher(): + now = datetime.now() + date_format = "%Y-%m-%d %H:%M:%S" + now_string = now.strftime(date_format) + assert now_string == is_datetime_string(date_format) + start_text = f"{now_string}: Martín created this awesome test" + assert start_text == is_string(starts_with=is_datetime_string(date_format)) + end_text = f"Someone decided to add some extra text to the end on: {now_string}" + assert end_text == is_string(ends_with=is_datetime_string(date_format)) + contains_text = f"I once ate a sandwich, was on {now_string}, or maybe two" + assert contains_text == is_string(contains=is_datetime_string(date_format)) + assert start_text == is_string(contains=is_datetime_string(date_format)) + assert end_text == is_string(contains=is_datetime_string(date_format)) + + def test_uuids(): assert uuid4() == is_uuid() assert str(uuid4()) == is_uuid() diff --git a/src/tests/matchers/test_between.py b/src/tests/matchers/test_between.py index 763d0a9..c475a43 100644 --- a/src/tests/matchers/test_between.py +++ b/src/tests/matchers/test_between.py @@ -1,6 +1,7 @@ import pytest from pytest_matchers.matchers import Between, Eq +from pytest_matchers.matchers.between import between_matcher def test_create(): @@ -129,3 +130,17 @@ def test_matches_without_max(): assert matcher == 30000 assert matcher != 0 assert matcher != "string" + + +def test_between_matcher(): + matcher = between_matcher(1, 2, None, None, None) + assert isinstance(matcher, Between) + assert matcher == 1 + matcher = between_matcher(None, 2, True, None, None) + assert isinstance(matcher, Between) + assert matcher == 2 + matcher = between_matcher(1, None, None, False, None) + assert isinstance(matcher, Between) + assert matcher == 2 + matcher = between_matcher(None, None, True, None, None) + assert matcher is None diff --git a/src/tests/matchers/test_contains.py b/src/tests/matchers/test_contains.py index 0dca552..44fb559 100644 --- a/src/tests/matchers/test_contains.py +++ b/src/tests/matchers/test_contains.py @@ -1,9 +1,17 @@ +import pytest + +from pytest_matchers import is_string, one_of from pytest_matchers.matchers import Contains +from pytest_matchers.matchers.contains import contains_matcher def test_create(): matcher = Contains(20) assert isinstance(matcher, Contains) + with pytest.raises(ValueError, match="At least one value must be provided"): + Contains() + matcher = Contains("ab", 10, []) + assert isinstance(matcher, Contains) def test_repr(): @@ -11,13 +19,23 @@ def test_repr(): assert repr(matcher) == "To contain 20" matcher = Contains("ab") assert repr(matcher) == "To contain 'ab'" + matcher = Contains("ab", 10, []) + assert repr(matcher) == "To contain 'ab', 10, []" def test_concatenated_repr(): matcher = Contains(20) - assert matcher.concatenated_repr() == "containing 20" + assert matcher.concatenated_repr() == "containing something expected equal to 20" matcher = Contains("ab") - assert matcher.concatenated_repr() == "containing 'ab'" + assert matcher.concatenated_repr() == "containing something expected equal to 'ab'" + matcher = Contains("ab", 10, []) + assert ( + matcher.concatenated_repr() + == "containing something expected equal to 'ab', and something equal to 10, " + "and something equal to []" + ) + matcher = Contains(is_string()) + assert matcher.concatenated_repr() == "containing something expected to be a string" def test_matches_list(): @@ -28,6 +46,15 @@ def test_matches_list(): assert matcher != 20 +def test_matches_list_with_matcher(): + matcher = Contains(is_string()) + assert matcher == ["string"] + assert matcher == ["string", "another"] + assert matcher == ["string", 20] + assert matcher != [20] + assert matcher != 20 + + def test_matches_string(): matcher = Contains("ab") assert matcher == "ab" @@ -37,3 +64,51 @@ def test_matches_string(): assert matcher != "ac" assert matcher != ["abc"] assert matcher != 20 + + +def test_matches_string_with_matcher(): + matcher = Contains(one_of("a", "b")) + assert matcher == "a" + assert matcher == "b" + assert matcher == "ab" + assert matcher == "ba" + assert matcher == "abc" + assert matcher == "dabc" + assert matcher == ["x", "a", "b", "y"] + assert matcher != "cdf" + assert matcher != ["x", "c", "d", "f"] + assert matcher != 20 + + +def test_matches_multiple_values(): + matcher = Contains("a", "b") + assert matcher == "ab" + assert matcher == "ba" + assert matcher == "abc" + assert matcher == "dabc" + assert matcher == ["x", "a", "b", "y"] + assert matcher != "ac" + matcher = Contains(*[10, 5]) + assert matcher == [10, 5, 30] + assert matcher == [10, 5] + assert matcher != [10, 6] + + +def test_matches_with_compound_matcher(): + matcher = Contains([is_string()]) + assert matcher == [["a"], "b"] + assert matcher != ["a"] + assert matcher != [[1], "a"] + assert matcher != "abc" + matcher = Contains([is_string(), is_string()]) + assert matcher != [["a"], "b"] + assert matcher != [["a", 1], "b", "c"] + assert matcher == ["b", ["a", "x"], "c"] + + +def test_contains_matcher(): + matcher = contains_matcher("string") + assert isinstance(matcher, Contains) + assert matcher == "string" + matcher = contains_matcher(None) + assert matcher is None diff --git a/src/tests/matchers/test_ends_with.py b/src/tests/matchers/test_ends_with.py index 6423bb3..4d100bf 100644 --- a/src/tests/matchers/test_ends_with.py +++ b/src/tests/matchers/test_ends_with.py @@ -1,4 +1,6 @@ +from pytest_matchers import is_number, is_string, one_of from pytest_matchers.matchers import EndsWith +from pytest_matchers.matchers.ends_with import ends_with_matcher def test_create(): @@ -17,9 +19,9 @@ def test_repr(): def test_concatenated_repr(): matcher = EndsWith("a") - assert matcher.concatenated_repr() == "ending with 'a'" + assert matcher.concatenated_repr() == "with ending expected equal to 'a'" matcher = EndsWith([1, 2]) - assert matcher.concatenated_repr() == "ending with [1, 2]" + assert matcher.concatenated_repr() == "with ending expected equal to [1, 2]" def test_matches_string(): @@ -34,6 +36,17 @@ def test_matches_string(): assert matcher != ["x", "bc"] +def test_matches_string_with_matcher(): + matcher = EndsWith(one_of("ab", "ac")) + assert matcher == "ab" + assert matcher == "ac" + assert matcher != "abc" + assert matcher == "bac" + assert matcher == "cab" + assert matcher != "dabc" + assert matcher != ["x", "bc", "y"] + + def test_matches_list(): matcher = EndsWith([1, 3]) assert matcher == [2, 1, 3] @@ -42,3 +55,35 @@ def test_matches_list(): assert matcher != [10, 20, 30] assert matcher != "string" assert matcher != 20 + + +def test_matches_list_with_matcher(): + matcher = EndsWith(one_of([1], [2])) + assert matcher == [1, 3, 2] + assert matcher == [2] + assert matcher == [2, 1] + assert matcher != [1, 2, 3] + assert matcher != "string" + assert matcher != 20 + + +def test_matches_with_compound_matcher(): + matcher = EndsWith([is_string()]) + assert matcher == ["a", "b"] + assert matcher == ["a"] + assert matcher != ["a", 1] + assert matcher != "abc" + matcher = EndsWith([is_string(), is_number()]) + assert matcher == ["a", 1] + assert matcher == ["x", 28, "z", 10] + assert matcher != ["a"] + assert matcher != [1, "a"] + assert matcher != "abc" + + +def test_ends_with_matcher(): + matcher = ends_with_matcher("string") + assert isinstance(matcher, EndsWith) + assert matcher == "string" + matcher = ends_with_matcher(None) + assert matcher is None diff --git a/src/tests/matchers/test_is_instance.py b/src/tests/matchers/test_is_instance.py index 94bd39a..93beded 100644 --- a/src/tests/matchers/test_is_instance.py +++ b/src/tests/matchers/test_is_instance.py @@ -1,4 +1,5 @@ from pytest_matchers.matchers import IsInstance +from pytest_matchers.matchers.is_instance import is_instance_matcher def test_create(): @@ -28,3 +29,11 @@ def test_matches(): assert 3 == IsInstance(int) assert 3 != IsInstance(str) assert "string" != IsInstance(int) + + +def test_is_instance_matcher(): + matcher = is_instance_matcher(str) + assert isinstance(matcher, IsInstance) + assert matcher == "string" + matcher = is_instance_matcher(None) + assert matcher is None diff --git a/src/tests/matchers/test_is_string.py b/src/tests/matchers/test_is_string.py index c5e6c90..8b2552e 100644 --- a/src/tests/matchers/test_is_string.py +++ b/src/tests/matchers/test_is_string.py @@ -22,11 +22,11 @@ def test_repr(): matcher = String() assert repr(matcher) == "To be a string" matcher = String(contains="ab") - assert repr(matcher) == "To be a string containing 'ab'" + assert repr(matcher) == "To be a string containing something expected equal to 'ab'" matcher = String(starts_with="ab") - assert repr(matcher) == "To be a string starting with 'ab'" + assert repr(matcher) == "To be a string with start expected equal to 'ab'" matcher = String(ends_with="bc") - assert repr(matcher) == "To be a string ending with 'bc'" + assert repr(matcher) == "To be a string with ending expected equal to 'bc'" matcher = String(length=1) assert repr(matcher) == "To be a string with length of 1" matcher = String(min_length=1, max_length=3) @@ -39,8 +39,9 @@ def test_repr(): max_length=8, ) assert ( - repr(matcher) == "To be a string with length between 1 and 8 and containing 'ab'" - " and starting with 'ab' and ending with 'bc'" + repr(matcher) == "To be a string with length between 1 and 8 and " + "containing something expected equal to 'ab'" + " and with start expected equal to 'ab' and with ending expected equal to 'bc'" ) diff --git a/src/tests/matchers/test_length.py b/src/tests/matchers/test_length.py index cde56f0..3a1b62f 100644 --- a/src/tests/matchers/test_length.py +++ b/src/tests/matchers/test_length.py @@ -1,6 +1,7 @@ import pytest from pytest_matchers.matchers import Length +from pytest_matchers.matchers.length import length_matcher def test_create(): @@ -74,3 +75,14 @@ def test_matches_without_limits(): assert matcher.matches([1]) assert matcher.matches([1, 2]) assert not matcher.matches(1) + + +def test_length_matcher(): + matcher = length_matcher(None, 2, 3) + assert isinstance(matcher, Length) + assert matcher == "ab" + matcher = length_matcher(None, None, 3) + assert isinstance(matcher, Length) + assert matcher == [1, 2, 3] + matcher = length_matcher(None, None, None) + assert matcher is None diff --git a/src/tests/matchers/test_starts_with.py b/src/tests/matchers/test_starts_with.py index b3cb012..a4e72f9 100644 --- a/src/tests/matchers/test_starts_with.py +++ b/src/tests/matchers/test_starts_with.py @@ -1,4 +1,6 @@ +from pytest_matchers import is_number, is_string, one_of from pytest_matchers.matchers import StartsWith +from pytest_matchers.matchers.starts_with import starts_with_matcher def test_create(): @@ -17,9 +19,9 @@ def test_repr(): def test_concatenated_repr(): matcher = StartsWith("a") - assert matcher.concatenated_repr() == "starting with 'a'" + assert matcher.concatenated_repr() == "with start expected equal to 'a'" matcher = StartsWith([1, 2]) - assert matcher.concatenated_repr() == "starting with [1, 2]" + assert matcher.concatenated_repr() == "with start expected equal to [1, 2]" def test_matches_string(): @@ -34,6 +36,18 @@ def test_matches_string(): assert matcher != ["ab", "x"] +def test_matches_string_with_matcher(): + matcher = StartsWith(one_of("ab", "ac")) + assert matcher == "ab" + assert matcher == "ac" + assert matcher == "abc" + assert matcher == "acb" + assert matcher != "dabc" + assert matcher != ["ab", "x", "y"] + assert matcher != "ad" + assert matcher != 20 + + def test_matches_list(): matcher = StartsWith([1, 3]) assert matcher == [1, 3, 2] @@ -42,3 +56,32 @@ def test_matches_list(): assert matcher != [10, 20, 30] assert matcher != "string" assert matcher != 20 + + +def test_matches_list_with_matcher(): + matcher = StartsWith(one_of([1], [2])) + assert matcher == [1, 3, 2] + assert matcher == [2, 3] + assert matcher != [20, 1, 3, 30] + + +def test_matches_with_compound_matcher(): + matcher = StartsWith([is_string()]) + assert matcher == ["a", "b"] + assert matcher == ["a"] + assert matcher != [1, "a"] + assert matcher != "abc" + matcher = StartsWith([is_string(), is_number()]) + assert matcher == ["a", 1] + assert matcher == ["x", 28, "z", 10] + assert matcher != ["a"] + assert matcher != [1, "a"] + assert matcher != "abc" + + +def test_starts_with_matcher(): + matcher = starts_with_matcher("string") + assert isinstance(matcher, StartsWith) + assert matcher == "string" + matcher = starts_with_matcher(None) + assert matcher is None diff --git a/src/tests/test_main.py b/src/tests/test_main.py index fbd6811..f94c7be 100644 --- a/src/tests/test_main.py +++ b/src/tests/test_main.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime +from datetime import datetime, time from unittest.mock import MagicMock from uuid import uuid4 @@ -19,13 +19,19 @@ is_datetime, is_datetime_string, is_dict, + is_float, is_instance, + is_int, + is_iso_8601_date, + is_iso_8601_datetime, + is_iso_8601_time, is_json, is_list, is_number, is_strict_dict, is_string, is_uuid, + not_empty_string, one_of, same_value, ) @@ -81,6 +87,7 @@ def test_is_list(): def test_is_string(): assert "string" == is_string() + assert "" == is_string() assert "string" == is_string(starts_with="s") assert "string" == is_string(ends_with="g") assert "string" == is_string(contains="ri") @@ -96,6 +103,14 @@ def test_is_string(): assert "string" != is_string(max_length=5) +def test_not_empty_string(): + assert "string" == not_empty_string() + assert "" != not_empty_string() + assert "string" != not_empty_string(min_length=7) + assert "string" != not_empty_string(max_length=5) + assert "string" != not_empty_string(min_length=7, max_length=5) + + def test_is_number(): assert 3 == is_number() assert "3" == is_number() @@ -112,6 +127,31 @@ def test_is_number(): assert 3 != is_number(min_value=4, max_value=7) +def test_is_int(): + assert 3 == is_int() + assert 3.5 != is_int() + assert 3 == is_int(min_value=2) + assert 3 == is_int(max_value=4) + assert 3 == is_int(min_value=2, max_value=4) + assert 3 != is_int(min_value=4) + assert 3 != is_int(max_value=2) + assert 3 != is_int(min_value=4, max_value=2) + assert 3 != is_int(min_value=4, max_value=7) + + +def test_is_float(): + assert 3.5 == is_float() + assert 3 != is_float() + assert 3.0 == is_float() + assert 3.5 == is_float(min_value=2) + assert 3.5 == is_float(max_value=4) + assert 3.5 == is_float(min_value=2, max_value=4) + assert 3.5 != is_float(min_value=4) + assert 3.5 != is_float(max_value=2) + assert 3.5 != is_float(min_value=4, max_value=2) + assert 3.5 != is_float(min_value=4, max_value=7) + + def test_one_of(): assert 1 == one_of(1, 4) assert 4 == one_of(1, 4) @@ -190,6 +230,33 @@ def test_is_datetime_string(): ) +def test_is_iso_8601_datetime(): + assert "2021-01-01T00:00:00" == is_iso_8601_datetime() + assert "2021-01-01T21:00:00" == is_iso_8601_datetime(min_value=datetime(2021, 1, 1)) + assert "2021-01-01T20:00:00" == is_iso_8601_datetime(max_value=datetime(2021, 1, 2)) + assert "2021-01-01T15:00:00" == is_iso_8601_datetime( + min_value=datetime(2021, 1, 1), + max_value=datetime(2021, 1, 2), + ) + + +def test_is_iso_8601_date(): + assert "2021-01-01" == is_iso_8601_date() + assert "2021-01-01" == is_iso_8601_date(min_value=datetime(2021, 1, 1)) + assert "2021-01-01" == is_iso_8601_date(max_value=datetime(2021, 1, 1)) + assert "2021-01-01" == is_iso_8601_date( + min_value=datetime(2021, 1, 1), + max_value=datetime(2021, 1, 2), + ) + + +def test_is_iso_8601_time(): + assert "00:00:00" == is_iso_8601_time() + assert "21:00:00" == is_iso_8601_time(min_value=time(20)) + assert "20:00:00" == is_iso_8601_time(max_value=time(21)) + assert "15:00:00" == is_iso_8601_time(min_value=time(14), max_value=time(16)) + + def test_same_value(): assert 3 == same_value() assert 4 == same_value() diff --git a/src/tests/utils/test_matcher_utils.py b/src/tests/utils/test_matcher_utils.py index be4e182..8519773 100644 --- a/src/tests/utils/test_matcher_utils.py +++ b/src/tests/utils/test_matcher_utils.py @@ -1,23 +1,11 @@ from pytest_matchers.matchers import ( Anything, - Between, - Contains, - EndsWith, Eq, - IsInstance, - Length, - StartsWith, ) from pytest_matchers.utils.matcher_utils import ( as_matcher, - between_matcher, - contains_matcher, - ends_with_matcher, - is_instance_matcher, - length_matcher, matches_or_none, partial_matches_or_none, - starts_with_matcher, ) @@ -51,60 +39,3 @@ def test_partial_matches_or_none(): partial_matcher = partial_matches_or_none(matcher) assert partial_matcher(1) assert partial_matcher(2) - - -def test_is_instance_matcher(): - matcher = is_instance_matcher(str) - assert isinstance(matcher, IsInstance) - assert matcher == "string" - matcher = is_instance_matcher(None) - assert matcher is None - - -def test_contains_matcher(): - matcher = contains_matcher("string") - assert isinstance(matcher, Contains) - assert matcher == "string" - matcher = contains_matcher(None) - assert matcher is None - - -def test_length_matcher(): - matcher = length_matcher(None, 2, 3) - assert isinstance(matcher, Length) - assert matcher == "ab" - matcher = length_matcher(None, None, 3) - assert isinstance(matcher, Length) - assert matcher == [1, 2, 3] - matcher = length_matcher(None, None, None) - assert matcher is None - - -def test_starts_with_matcher(): - matcher = starts_with_matcher("string") - assert isinstance(matcher, StartsWith) - assert matcher == "string" - matcher = starts_with_matcher(None) - assert matcher is None - - -def test_ends_with_matcher(): - matcher = ends_with_matcher("string") - assert isinstance(matcher, EndsWith) - assert matcher == "string" - matcher = ends_with_matcher(None) - assert matcher is None - - -def test_between_matcher(): - matcher = between_matcher(1, 2, None, None, None) - assert isinstance(matcher, Between) - assert matcher == 1 - matcher = between_matcher(None, 2, True, None, None) - assert isinstance(matcher, Between) - assert matcher == 2 - matcher = between_matcher(1, None, None, False, None) - assert isinstance(matcher, Between) - assert matcher == 2 - matcher = between_matcher(None, None, True, None, None) - assert matcher is None