[Product-Developers] Re: Mock testing
Martin Aspeli
optilude at gmx.net
Wed Jul 2 20:41:34 UTC 2008
Hi Maurits,
> I have not tried any code (also not from Dexterity), only read your
> article. Some things are not clear to me.
>
> - At the end of the first mock example you say: "We must remember to put
> the test case into replay mode, using self.replay()." Why? What does
> that do?
Before you put the mock into replay mode, it's in "record" mode. All
operations you do on it are essentially expectations. For example:
mymock = self.mocker.mock()
self.expect(mymock.foo()).result(True)
Now, if I do
mymock.foo()
without putthing the mock in replay mode, it's basically just recording
an expectation that foo will be called. In fact, the above could have
been rewritten as:
mymock = self.mocker.mock()
mymock.foo()
self.mocker.result(True)
though I prefer the self.expect() chaining syntax as it's more compact.
Only when the mock is put in replay mode will it actually exhibit the
behaviour and check that it's being called in the expected way. You do
that with
self.replay()
This is how most mock libraries work. You start off in record mode,
doing operations on the object that "record" expectations about how it
should be called. You then put it into "replay" mode and call the code
under test (initialised to use the mock, obviously). The mock library
then checks that recorded operations actually happen (in the way that
they were recorded) and throws assertion errors if they're not. Finally,
you do a verify + restore (implicit in the unit test) that ensures no
steps were missed and unpatches anything patched for the test.
> - In the third mock example you say that we "set the expectation that
> lookup_schema() should be called on it once", stressing the word
> 'once'. Which part of the test code does that? From the paragraphs
> after that I *guess* that whenever the code is this:
>
> self.expect(...).result(...)
>
> it implicitly means this:
>
> self.expect(...).result(...).count(1, 1)
>
> Is that a correct guess?
Yes.
> If so, I think you should mention that
> explicitly, perhaps already in the first mock example.
In record mode, the assumption is that if you record an action once, you
meant for it to happen once. Let's say I have a mock and I expect it to
be called twice, returning one thing the first time and something else
the second time:
magic_eight_ball = self.mocker.mock()
self.expect(magic_eight_ball()).result("No")
self.expect(magic_eight_ball()).result("Yes")
> BTW, if that is correct then I would find "count(0, None)" a more
> logical default, so by default no restriction on the number of times
> a mocked method is called.
Most mock libraries don't work like this. They fail when a method is
called too many times (and will tell you so). I think it's (marginally)
better to be explicit if you don't care how many times something's
called. It's likely that if you expected something to be called, and
it's called 0 times, then it's an error, and if it's called dozens of
times, it's also an error.
> And a question: is there next to mock_utility also something like
> mock_tool, so you could you this to mock for example the
> portal_catalog? I guess something along these lines might work:
>
> from whereever import CatalogTool
> catalog_mock = self.mocker.proxy(CatalogTool())
> context_mock = self.mocker.mock()
> self.expect(context_mock.portal_catalog).result(catalog_mock).count(0,None)
No. There could be, but bear in mind that mock tests are not running in
a PloneTestCase (which is why they're so quick) - at least not normally
(no reason they couldn't be, but then you're really doing an integration
test). As such, if your code calls getToolByName(), you'd probably do a
replace() on this.
A generic mock_tool() could look like this, though:
from mocker import ANY
class MockTestCase(...):
...
getToolByName_mock = None
def mock_tool(self, tool_mock, name, expected_context=ANY,
min=1, max=None):
mock = self.getToolByName_mock
if mock is None:
self.getToolByName_mock = mock = \
self.mocker.replace('Products.CMFCore.utils.getToolByName')
self.expect(mock(expected_context,
name)).result(tool_mock).count(min, max)
Untested and possibly refactorable of course.
Martin
--
Author of `Professional Plone Development`, a book for developers who
want to work with Plone. See http://martinaspeli.net/plone-book
More information about the Product-Developers
mailing list