summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS6
-rw-r--r--mocker.py34
-rwxr-xr-xtest.py62
3 files changed, 102 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index 9ff0dbc..8d43cb5 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,12 @@
easy creation of temporary files/directories, and ensure that they
get removed after each test method runs.
+- Added MockerTestCase.assertMethodsMatch(). It will verify if all
+ public methods found in the class passed as the first argument are
+ also present in the class passed as the second argument, and that
+ they accept the same arguments. This is useful to verify if a fake
+ or stub class have the same API as the real class being simulated.
+
- If the replay() method is called twice, expectations will be fully
reset so that several similar tests may be performed in a row by
just calling replay() again.
diff --git a/mocker.py b/mocker.py
index fe27892..7d7160c 100644
--- a/mocker.py
+++ b/mocker.py
@@ -192,12 +192,46 @@ class MockerTestCase(unittest.TestCase):
raise self.failureException(msg or "abs(%r - %r) <= %r" %
(first, second, tolerance))
+ def failUnlessMethodsMatch(self, first, second):
+ """Assert that public methods in C{first} are present in C{second}.
+
+ This method asserts that all public methods found in C{first} are also
+ present in C{second} and accept the same arguments. C{first} may
+ have its own private methods, though, and may not have all methods
+ found in C{second}. Note that if a private method in C{first} matches
+ the name of one in C{second}, their specification is still compared.
+
+ This is useful to verify if a fake or stub class have the same API as
+ the real class being simulated.
+ """
+ first_methods = dict(inspect.getmembers(first, inspect.ismethod))
+ second_methods = dict(inspect.getmembers(second, inspect.ismethod))
+ for name, first_method in first_methods.items():
+ first_argspec = inspect.getargspec(first_method)
+ first_formatted = inspect.formatargspec(*first_argspec)
+
+ second_method = second_methods.get(name)
+ if second_method is None:
+ if name[:1] == "_":
+ continue # First may have its own private methods.
+ raise self.failureException("%s.%s%s not present in %s" %
+ (first.__name__, name, first_formatted, second.__name__))
+
+ second_argspec = inspect.getargspec(second_method)
+ if first_argspec != second_argspec:
+ second_formatted = inspect.formatargspec(*second_argspec)
+ raise self.failureException("%s.%s%s != %s.%s%s" %
+ (first.__name__, name, first_formatted,
+ second.__name__, name, second_formatted))
+
+
assertIs = failUnlessIs
assertIsNot = failIfIs
assertIn = failUnlessIn
assertNotIn = failIfIn
assertApproximates = failUnlessApproximates
assertNotApproximates = failIfApproximates
+ assertMethodsMatch = failUnlessMethodsMatch
# The following is provided for compatibility with Twisted's trial.
assertIdentical = assertIs
diff --git a/test.py b/test.py
index 1d8acc6..62c1230 100755
--- a/test.py
+++ b/test.py
@@ -481,6 +481,65 @@ class MockerTestCaseTest(unittest.TestCase):
except AssertionError:
self.fail("AssertionError shouldn't be raised")
+ def test_fail_unless_methods_match_raises_on_different_method(self):
+ class Fake(object):
+ def method(self, a): pass
+ class Real(object):
+ def method(self, b): pass
+ try:
+ self.test.failUnlessMethodsMatch(Fake, Real)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Fake.method(self, a) != "
+ "Real.method(self, b)")
+ else:
+ self.fail("AssertionError not raised")
+
+ def test_fail_unless_methods_match_raises_on_missing_method(self):
+ class Fake(object):
+ def method(self, a): pass
+ class Real(object):
+ pass
+ try:
+ self.test.failUnlessMethodsMatch(Fake, Real)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Fake.method(self, a) not present "
+ "in Real")
+ else: self.fail("AssertionError not raised")
+
+ def test_fail_unless_methods_match_succeeds_on_missing_priv_method(self):
+ class Fake(object):
+ def _method(self, a): pass
+ class Real(object):
+ pass
+ try:
+ self.test.failUnlessMethodsMatch(Fake, Real)
+ except AssertionError, e:
+ self.fail("AssertionError shouldn't be raised")
+
+ def test_fail_unless_methods_match_raises_on_different_priv_method(self):
+ class Fake(object):
+ def _method(self, a): pass
+ class Real(object):
+ def _method(self, b): pass
+ try:
+ self.test.failUnlessMethodsMatch(Fake, Real)
+ except AssertionError, e:
+ self.assertEquals(str(e), "Fake._method(self, a) != "
+ "Real._method(self, b)")
+ else:
+ self.fail("AssertionError not raised")
+
+ def test_fail_unless_methods_match_succeeds(self):
+ class Fake(object):
+ def method(self, a): pass
+ class Real(object):
+ def method(self, a): pass
+ obj = []
+ try:
+ self.test.failUnlessMethodsMatch(Fake, Real)
+ except AssertionError:
+ self.fail("AssertionError shouldn't be raised")
+
def test_aliases(self):
get_method = MockerTestCase.__dict__.get
@@ -502,6 +561,9 @@ class MockerTestCaseTest(unittest.TestCase):
self.assertEquals(get_method("assertNotApproximates"),
get_method("failIfApproximates"))
+ self.assertEquals(get_method("assertMethodsMatch"),
+ get_method("failUnlessMethodsMatch"))
+
def test_twisted_trial_aliases(self):
get_method = MockerTestCase.__dict__.get