1.3.3. Chainable Mock Objects

1.3.3.1. Overview

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:

  • I expect some method invocation exactly once
  • This must happen after another invocation labeled "other-ident"
  • The method must be called with a value equal to 321
  • Then the method will return 123
  • This invocation has an identifier called "ident"

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().

1.3.3.2. Stubs And Expectations

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.

1.3.3.3. Invocation Matchers

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:

  • once(): exactly one single invocation
  • exactly(): an exact invocation count
  • never(): never invoke this method
  • atLeast(): a minimum invocation count
  • atLeastOnce(): invoke the method at least once
  • atMost(): a maximum invocation count

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.

1.3.3.4. Invocation Order

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().

1.3.3.5. Parameter Constraints

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:

  • IsEqual/eq() tests equality
  • IsCloseTo/eq(a,b) tests near equality
  • IsGreaterThan/gt() is true for higher values a reference value
  • IsLessThan/lt() is true for lower values than a reference value
  • IsNot/eq() negates another contraint
  • And/logic_and() provides a logical-and of two constraints
  • Or/logic_or() provides a logical-or of two constraints
  • IsAnything/any() is always true
  • IsNothing/nothing() is never true
  • StringContains/stringContains() checks for the occurence of a substring
  • IsInstanceOf/isA() tests if an object is of a type

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.

1.3.3.6. Custom Matchers

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().

1.3.3.7. Return Values and Exceptions

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().

1.3.3.8. Some Examples

To get a chainable mock object you must:

  • Derive and define your object with the methods you need
  • Create a chainer for each method and set up the behaviour

The following listing contains a short example and explains some of the features.


  class MyInterfaceClass 1
  {
    public:

     virtual int access(unsigned &index) = 0;
  }

  class MyChainableMockObject : public ChainableMockObject,  2
                                public MyInterfaceClass
  {
    public:
      MyChainableMockObject(const String &name)
        : ChainableMockObject(name, 0)
        , MOCKPP_CONSTRUCT_MEMBERS_FOR_CHAINABLE1(access) 3
      {}

      MOCKPP_CHAINABLE1(MyChainableMockObject, int, access, unsigned); 4
  };

  MyChainableMockObject mco("mco");

  MOCKPP_CHAINER_FOR(MyChainableMockObject, access) chainer (&mco); 5

  chainer.setDefaultStub(new ThrowStub(std::string("throw-default")));           6
  chainer.stubs().with(1).will(new ReturnStub(13));                              7
  chainer.expects(once()).with((2).id("called-with-2");                          8
  chainer.stubs().after(otherObject, "some-label").will(new ReturnStub(2222));   9

  // use the object

  mco.verify(); 10

1

Define your interface class.

2

Derive your mock object class from ChainableMockObject and the interface class.

3

According to the type of method and the number of parameters it takes add a macro that does the construction part for all the necessary internal helper variables for that method.

4

Similarly add another macro that implements the internal variables and helper methods.

5

Create a chainer object for the method to set up it's behaviour.

6

Add some behaviour: tell the object to throw an exception when no other expectations match.

7

More specific behaviour: depending on the parameters passed it shall respond with certain return values.

8

Add a label to an invocation for further reference.

9

After an invocation of some other object's method has occured return a different value.

10

After the tests have completed verify all conditions that are still pending, for example unused return values or exceptions.