1.3. Advanced Expectations

1.3.1. Introduction to Advanced Mock Objects

1.3.1.1. The Standard Working Method

To emulate the behaviour of an object in your production code you need more than just single values that meet your expectations. Usually you need a collection of the different expectation types and some code to generate a deterministic and predictible behaviour to test a limited area.

The following methods all require a separation of the code under test and the code using it. But most of the times you would split the two parts anyway to increase maintainability.


  class MyInterface 1
  {
    public:

      virtual void theMethod(unsigned param) = 0;
  };

  class MyProductionClass : public MyInterface 2
  {
    public:

      virtual void theMethod(unsigned param)
      {
        ...
      }
  };

  class MyMockObject : public MyInterface 3
  {
      mockpp::ExpectationList<unsigned> methParams;

    public:

      MyMockObject(mockpp::String name, Verifiable *v)
        : MockObject(name, v)
        , methParams("method parameters", this)
      {
        methParams.addExpected(1);
        methParams.addExpected(2);
      }

      virtual void theMethod(unsigned param)
      {
        methParams.addActual(param);
        ...
      }
  };


  MyMockObject mo (MOCKPP_PCHAR("mockObject"), 0);
  myMethodUnderTest(&mo); 4
  mo.verify();


  MyProductionClass pro;
  myMethodUnderTest(&pro); 5

1

Define an interface class which contains all the methods that must simulate some real world behaviour. This interface class is also used to implement the real world class.

The intention is to pass a real world object in the running application or a mock object while executing tests.

2

Derive your real world production code from the abstract base class to implement our interface as you need it.

3

Derive the mock object and implement it with a minimum of the functionality required in an absolutely deterministic way. In the example we set up a list of expected parameters to the method and have it verified by the mock object automatically.

4

Instantiate the mock object and pass it to the method you want to test while you are in the test routines. After completing the test code call verify() to check all pending expectations.

5

To run the production code instantiate the real object and pass it to the method.

1.3.1.2. The Main Mock Object Classes

Since most of tasks concerning mock objects occur frequently and in a similar manner there are some classes which support automated behaviour with different approaches. Depending on your personal taste or special problems you may choose the one or the other.

  • The first possibility is modeled by a class called VisitableMockObject. It was named like this because you set up part of it's behaviour by recording an execution path for the tests, thus by "visiting" the object. This approach is rather strict because the invocation order and parameter values must match exactly. Unfortunatley it is almost impossible to add new capabilities.
  • The second approach is represented by the class ChainableMockObject. The name stems from the fact that you define the behaviour with a "chain" of invocations of a helper object. Each chain link sets a contraint, a return value or some other aspect. The big benefit from this approach is the extensiblity concerning the behaviour by simply feeding it new objects which reflect your needs. Therefor it is far more flexible in execution: everything depends on user defined objects outside the mock object. A large set of such classes is already included.

More detailed explanations follow in later chapters.

The basic principle for the use of these classes is the same for both methods:

  • Derive from the interface class and the according mock object class.
  • Add the methods and their internal variables with one of the predefined pair of macros.
  • Use one of the according macros to construct a helperobject to set up the detailed behaviour.

[Warning]But let me warn you:

If you take the wrong macro or maybe just have a small typo you will get loads of error messages all pointing to the same line. You might have no idea where to start and search the problem. I strongly recommend to take little steps when setting up such objects: add one single method and compile. Continue only if it compiled flawlessly.

This warning shouldn't scare you away. It isn't really that complicated and once you have understood the working method of the macros you may have understood half of mockpp. To get started I recommend to use the "recipies" below.

If you want to have an overview what is created behind the scenes, look at chainable-template.h and visitable-template.h which contain example classes with some methods. They were preprocessed, automatically indented and are quite readable. This knowledge might become useful when you need to find out more about the referenced objects and their internal helper variables when runtime errors occur.

1.3.1.3. Choosing the macros

There are sets of macros for const and non-const methods, the visitable and the chainable way and each combination of them with up to five parameters. I assume you will not want to have code with more than 5 parameters. But if you really think you need more, take the macros with 5 parameters and try to extend it. It is really straightforward and please don't forget to extend the test files. You may then of course send me the result for inclusion and further maintainance.

Due to the nature of macros there are some limitations concerning the automatically generated methods and the according internal helper variables.

  • Every type in the parameter list may need a default constructor.
  • Pointer values may be compared with their address and not with the value they point to.
  • With the above macros only const & X types are possible in the parameter list. You don't actually see it but that's what internally happens. If you have a const parameter or a simple type you must use an extended set of macros which distinguishes between the method parameters and the declaration of the internal helper variables.
  • If you have overloaded methods you must use the extended macros as well. They extend the internal variables names which are derived form the method name. Otherwise there would be a conflict.
  • It may become necessary to implement operator<<() as debug output is created this way. One approach is to provide a meaningful operator method. If this does not make much sense you may use a default template instead which simply outputs the raw classname. To avoid pontential conflicts this template must be enabled by setting the macro MOCKPP_ENABLE_DEFAULT_FORMATTER before including the first mockpp header file.

The total number of macros is quite huge but there is a scheme behind it:

  • The macro for the initializer list in the constructor is built like this:

    • Always start with MOCKPP_CONSTRUCT_MEMBERS_FOR_
    • If the method is of type void add VOID_
    • For chainable object append CHAINABLE
    • Visitable object need VISITABLE instead
    • Finally append the number of method parameters 0 .. 5

  • To determine the macro for the method write the following:

    • Always start with MOCKPP_
    • If the method is of type void add VOID_
    • In case the method is of type const write CONST_
    • For chainable object append CHAINABLE
    • whereas visitable object need VISITABLE
    • Extended macros with explicit method types need _EXT
    • Finally append the number of method parameters 0 .. 5

  • The macro for the helper object to set up the behaviour needs these steps:

    • Always start with MOCKPP_
    • For chainable object append CHAINER_FOR
    • whereas visitable object need CONTROLLER_FOR
    • Extended macros with explicit method types need _EXT

Here are some examples:

  • A void method with 5 parameters in a chainable mock object: MOCKPP_VOID_CHAINABLE5
  • A void and const method without parameters in a visitable mock object and extended parameter values and method name: MOCKPP_VOID_CONST_VISITABLE_EXT0
  • Macro for initializer list for a void method with 2 parameters: MOCKPP_CONSTRUCT_MEMBERS_FOR_VOID_CHAINABLE2

To see examples for all the macros have a look at the unit test files in the tests directory. Especially interesting are the files ChainableMockObject_* and VisitableMocObject_*.

If the available mock objects and methods don't give you fine enough control over your mock object there is only one solution: code and implement the desired behaviour manually. Look at chainable-template.h and visitable-template.h to get an idea of the standard implementation.

But I assume this will not happen too often because mock objects should only cover a very limited aspect, only what is currently really needed for a special test scope.

1.3.1.4. Automated Verification

After the last invocation to a mock object has occured you should always invoke verify() on the mock object to verify all pending expectations. Maybe there are some unused return objects or outstanding calls. If you use cppunit as test framework you should the use VerifyingTestCase from mockpp and register your mock objects on creation with it. In contrast to the standard TestCase from the cppunit package it invokes verify() on all mock objects after a test case passed. This way it is impossible to forget it.