When a method is invoked on a mock object, the mock object searches through its expectations from first to last to find one that matches the invocation. An expectation matches an invocation if all of its matching rules match the invocation. After the invocation, the matching expectation might stop matching further invocations. For example, an expects(once()) expectation only matches once and will be ignored on future invocations while an expects(atLeastOnce()) expectation will always be matched against invocations.
Specifying some behaviour with chainable mock objects is similar to describing it in words. You may say for example:
In C++ code you might express the same like this:
MOCKPP_CHAINER_FOR (MyClass, MyMethod) chainer (&myobject); chainer.expects(once()) .after("other-ident") .with(new IsEqual<int>(321)) .will(new ReturnStub(123)) .id("ident");
You need not provide each of these methods, only what you actually need. If you don't intend to return a special value: omit will() and the default return value will be used.
For void methods you may apply isVoid() instead of with() to explicitly express this fact.
The original java implementation contained an additional method called method() which passed the desired method name. This is completely missing in my C++ implementation. The reason is simple: C++ does not support reflection and therefor an application can't look into itself or create methods at runtime or do some other weird things. So I had to apply a handful of macro-magic instead. This led to the fact that you have to set up a different helper object for each method whereas jMock would use the same object for all calls of a mock object.
A note about the internals: each chain link returns a temporary builder object with its own set of methods to influence the mocked method and object. See directory builder for most of the files. The first method expects() returns an object of type InvocationMockerBuilder which provides method with(). And the last method returns a type IdentityBuilder which contains id().
Chainable mock objects provide stubbed and excpected methods. An expected method must occur as specified, otherwise the object will fail when verified. A stubbed method without specifiction may be invoked unlimited times or not at all but the test still passes.
In general, you will stub when you want to get some return value from a method whereas you expect when you intend to check the input parameters to a method. When invoked with a specification both methods do internally the same but you should use the correct one to express your intention.
The parameter to the methods expects() and stubs() must be some kind of invocation counter. The current release covers the most common cases with a set of classes. However these classes should not be used directly as they are not very readable. You should use one of the wrapper functions instead:
After setting up the behaviour and actually invoking the mock object the matcher specifications are processed one after the other in the order given in your source file. So if you wanted to return three different values 111, 222 and 333 you would write:
chainer.stubs(once()).will(new ReturnStub(111)); chainer.stubs(once()).will(new ReturnStub(222)); chainer.stubs(once()).will(new ReturnStub(333));
For obvious reasons you should not use atLeast() in the first expectation as is this would render the following lines useless.
Every single invocation to one of your mock objects may be labeled with a unique identifier. This identifier may then be used to determine that another invocation must happen prior or after it. You may as well demand that this decision has to be based on an invocation to a totally different mock object. And it is also possible to add more than one call to before() or after() if you want to have it match to more than one invocation in the same expectation chain.
For that purpose you use the methods before() or after().
When working with a VisitableMockObject you specify exactly which parameter value you expect. A ChainableMockObject on the other hand can work less strict. Specifying an exact match is common but you may as well allow a set of values or even any value. Or you demand that each value must be higher than the one in the previous invocation.
The distribution already contains a set of classes for common constraints. For convenience reasons and to enhance readability some of these classes have a counterpart in the form of a method.The most important are:
Creating your own constraints is quite easy. Look at the files in the constraint directory to get an idea how this can be done.
Use method with() to pass the constraints. Obviously this method has the same number of parameters as your mocked method.
The former sections explained standard methods for common use. But it is also possible to create your own rules for matching an invocation. If your rules depends on the parameters passed to the mocked method derive you custom class from InvocationMatcher. If it is independent from the parameters use TypelessMatcher instead. To express the non-standard use you should then pass the object to method match().
Return values from methods and exceptions are treated similar as exceptions are considered here not more than a special kind of return value. You may determine the return value based on the result of the parameters you passed with expects() and with() or you can optionally set a default action by passing it to setDefaultStub(). The default action takes place when no other expectation matches.
If you have a sequence of return values that shall be returned in subsequent invocations you can use a short hand method and pass the values via onConsecutiveCalls() to will().
To get a chainable mock object you must:
The following listing contains a short example and explains some of the features.
class MyInterfaceClass { public: virtual int access(unsigned &index) = 0; } class MyChainableMockObject : public ChainableMockObject, public MyInterfaceClass { public: MyChainableMockObject(const String &name) : ChainableMockObject(name, 0) , MOCKPP_CONSTRUCT_MEMBERS_FOR_CHAINABLE1(access) {} MOCKPP_CHAINABLE1(MyChainableMockObject, int, access, unsigned); }; MyChainableMockObject mco("mco"); MOCKPP_CHAINER_FOR(MyChainableMockObject, access) chainer (&mco); chainer.setDefaultStub(new ThrowStub(std::string("throw-default"))); chainer.stubs().with(1).will(new ReturnStub(13)); chainer.expects(once()).with((2).id("called-with-2"); chainer.stubs().after(otherObject, "some-label").will(new ReturnStub(2222)); // use the object mco.verify();