diff --git a/assertpy/assertpy.py b/assertpy/assertpy.py index 39abb20..3ad6f65 100644 --- a/assertpy/assertpy.py +++ b/assertpy/assertpy.py @@ -42,7 +42,7 @@ from .dict import DictMixin from .dynamic import DynamicMixin from .extracting import ExtractingMixin -from .exception import ExceptionMixin +from .exception import ExceptionMixin, _NoChainingMixin from .file import FileMixin from .helpers import HelpersMixin from .numeric import NumericMixin @@ -456,6 +456,10 @@ def error(self, msg): return self elif self.kind == 'soft': global _soft_err + if callable(self.val): + # replace all callable methods of AssertionBuilder instance with empty function if self.val is callable + self.__class__ = type(self.__class__.__name__, (self.__class__, _NoChainingMixin), {}) + out += " Any further chain assertions ignored." _soft_err.append(out) return self else: diff --git a/assertpy/exception.py b/assertpy/exception.py index 1649985..ccf1910 100644 --- a/assertpy/exception.py +++ b/assertpy/exception.py @@ -111,3 +111,16 @@ def some_func(a): self.val.__name__, self.expected.__name__, self._fmt_args_kwargs(*some_args, **some_kwargs))) + + +class _NoChainingMixin(object): + """Mixin that replaces all method calls with empty function.""" + def __getattribute__(self, item): + attr = super(_NoChainingMixin, self).__getattribute__(item) + if callable(attr) and item != "_do_nothing": + return self._do_nothing + return attr + + def _do_nothing(self, *args, **kwargs): + """Method that is used instead of all callable attributes of AssertionBuilder""" + return self diff --git a/tests/test_soft.py b/tests/test_soft.py index f163533..6781eba 100644 --- a/tests/test_soft.py +++ b/tests/test_soft.py @@ -111,6 +111,30 @@ def test_expected_exception_failure(): assert_that(out).contains("Expected to raise when called with ('baz').") +def test_expected_exception_failure_chaining(): + try: + with soft_assertions(): + # No exception + assert_that(func_ok).raises(RuntimeError).when_called_with('a').is_equal_to('dog').matches('cat') + # Exception type mismatch + assert_that(func_ok).raises(TypeError).when_called_with('a').contains('dog') + # Error message mismatch + assert_that(func_err).raises(RuntimeError).when_called_with('a').matches('dog') + fail('should have raised error') + except AssertionError as ex: + out = str(ex) + assert_that(out).contains( + "1. Expected to raise when called with ('a')." + " Any further chain assertions ignored." + ) + assert_that(out).contains( + "2. Expected to raise when called with ('a')." + " Any further chain assertions ignored." + ) + assert_that(out).contains("3. Expected to match pattern , but did not.") + assert_that(out).does_not_contain("4.") + + def func_ok(arg): pass