1.3.3. Chainable Mock Objects

1.3.3.1. Overview

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:


  chainer.expects(once())
         .with(new IsEqual<int>(321))
         .after("other-ident")
         .will(new ReturnStub(123))
         .id("ident");

You need not provide all 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 will() to explicitly express this fact.

When a method is invoked on a chainable 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.

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 and templates 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 expected methods. An expected method must occur as specified, otherwise the object will fail when verified. A stubbed method without specification 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:

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

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 against more than one invocation in the same expectation chain.

1.3.3.5. Parameter Constraints

When working with a VisitableMockObject you normally 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 most of these classes have a counterpart in the form of a wrapper function. The most important ones are:

  • IsEqual = eq() tests equality
  • IsSame = same() tests if two objects are the same
  • 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 = logic_not() 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 occurrence 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.

[Note]Caution with side effects

One might try to to combine two contraints using logic_or() or logic_and() respectively the according classes Or or And:


  mock.foo(logic_and<int>( eq<int>(0), outBound<int>(42)));

Unfortunately the resulting behaviour might not be as expected. By default the evaluation of the two contstriaints is shortcut. This means that the second constraint is not evaluated when the result is determined after the first. This is intended as it is the default behaviour in C/C++:


  if(evalA() && evalB())
     ...

evalB() is not executed if evalA() is false because this would not change the program flow anymore.

In this case the second constraint is called one to little. If you need a more determined behaviour when all constraints are evaluated you can pass a third parameter which is set to false and enforces full evaluation.

Use method with() to pass the constraints. Obviously this method has the same number of parameters as your mocked method.

1.3.3.5.1. Outbound Values

There are special constraints which are not actually real constraints. OutBound does not check for incoming parameter values but passes values back by a reference. This enables mocked methods like the following:


  void getByReference(int &ra)
  {
     ra = 123;
  }

The values are returned one after the other. Please understand that for chainable mock objects this is probably not the desired behaviour as the values are already stored when the parameters are just evaluated. The intended application is for visitable mock objects.

Similarly there is TriggeredOutBound which is used to store the value defered upon a trigger call. The first call to eval() does not store the value itself but a reference to the variable. To store the actual value a stub class like TriggerStub or ReturnValueAndTriggerStub is then used to trigger the copy action. This way the storing of the desired outbound value by reference is defered until the method would really be invoked. The calls to eval() are primarily to check for potential matches. Unfortunately a temporary shared pointer variable is needed since all the objects are completely independent from each other.

The following example would trigger the contraints in parameters 2 and 3 when the first parameters is 2:


  class UserInfo
  {
    virtual void vident(unsigned userno,
                        string &username,
                        string &useradr) = 0;
  };

  TriggeredOutbound<string> *tc_a;
  TriggeredOutbound<string> *tc_b;

  tc_a = new TriggeredOutbound<string>("my-name");
  tc_b = new TriggeredOutbound<string>("my-address");
  videnter.expects(once())
          .with(eq(2u),
                tc_a,
                tc_b)
          .will(trigger(tc_a, tc_b));

1.3.3.5.2. Constraints for Comparison

mockpp contains two constraint classes to compare objects. IsEqual is probably appropriate for most cases. It takes an object and stores it's value for later comparison.

IsSame on the other hand is intended to check for identical values. This usually means to compare the pointer values of two objects. There is a template function isSameComparison which implements this default behaviour. If you need a differing implementation for a certain class you can provide a specialised template. This may happen if you are using a shared pointer and don't want to compare the helper but the shared object at the end of the pointers.

For a similar reason there is another templated function named isEqualComparison. Both functions are useful if you need to compare objects but can't or don't want to implement the according operator==.

[Note]Potential backwards compatibility break

mockpp up to version 1.9.3 implemented IsEqual and IsSame in exactly the same manner. Unfortunately this may now lead to dangling pointers and crashes if existing code refers to temporary objects.

The following fragment shows the problem. When constructing the object a temporary string is created and this pointer is passed to the constructor of IsEqual. After this statement the string object is destroyed and this pointer becomes invalid.


   IsSame<std::string> same ("string");

To avoid this problem you have to create a variable which must exist until the test completes.


   std::string str ("string");
   IsSame<std::string> same (str);

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 depend on the parameters passed to the mocked method derive your 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().

But please keep in mind that custom matchers can virtually do everything. Most of the time you want to check method parameters and therefor you probably should create a constraint class derived from Constraint<T>. See also the files below mockpp/constraint as a starting point.

1.3.3.7. Return Values and Exceptions

Return values from methods and exceptions are treated similar. Exceptions are considered 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. Create a Chainer Object Using Macros

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<int>(make_throwable(1)));  6
  chainer.stubs()
         .with(eq(1))
         .will(new ReturnStub<int>(13));                              7

  chainer.expects(once())
         .with((eq(2)))
         .id("called-with-2"); 8

  chainer.stubs()
         .after(otherObject, "some-label")
         .will(new ReturnStub<int>(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.

1.3.3.9. Using a Mock Method Chainer

The second approach with mock method templates is almost the same as before. The difference is to replace the macros and the resulting chainer object with the method object and forward the call to it.

  class MyChainableMockObject : public ChainableMockObject,
                                public MyInterfaceClass
  {
    public:
      MyChainableMockObject(const String &name)
        : ChainableMockObject(name, 0)
        , access_mocker("access", this)  1
      {}

      virtual int access(unsigned index) 2
      {
        return access_mocker.forward(index);
      }

      ChainableMockMethod1<int, unsigned> access_mocker;  3
  };

  MyChainableMockObject mco("mco");

  ChainableMockMethod1<int, unsigned> &chainer (mco.access_mocker);  4

  chainer.setDefaultStub(new ThrowStub<int>(make_throwable(1)));
  chainer.stubs()
         .with(eq(12u))
         .will(new ReturnStub<int>(13));  5

  // use the object

  mco.verify();

1

Construct the mock method helper object.

2

Provide the method as entry point and forward the call to the method which does the actual work.

3

Instantiate a mock method variable. Select the class according to the parameters the method takes. There also a template without a trailing number which handles an arbitrary parameter count.

4

For convenience create a reference to the mock method helper.

5

Set up the mock objects behaviour in the same manner as described above using the macros.