Simple Mock Objects for C++11

Simple Mock Objects for C++11

By Michael Rüegg

Overload, 20(110):19-21, August 2012


New C++11 features can be used to implement mock objects for unit tests. Michael Rüegg shows us how he does this in Mockator.

In our last article ‘Refactoring Towards Seams in C++’ (appeared in issue 108), we described how we can break dependencies in legacy code by applying seams with the help of our engineered refactorings. Once we have managed to apply seams, our code is not relying on fixed dependencies anymore, but instead asks for collaborators through dependency injection. Not only has our design greatly improved, but we are now also able to write unit tests for our code.

Sometimes it is impractical or impossible to exercise our code with real objects. If a real object supplies non-deterministic results, is slow or contains states that are difficult to create, then we might want to use mock objects to test our objects in isolation. In this article we present how we can mock objects by creating a small but useful mock object library that makes use of the new language features of C++11.

What is a seam?
A seam is a place in the code where we can alter behaviour without being forced to edit it in that place [ Feathers04 ]. Every seam has one important property: an enabling point. This is the place where we can choose between one behaviour or another. There are different kinds of seam types. C++ supports object, compile, preprocessor and link seams.

How to mock objects

To start with an example, consider the system under test (SUT) Trader shown in Listing 1. Trader has a fixed relationship to Nasdaq which makes it hard to test in isolation because its operations require network calls which we do want to avoid when we run our unit tests. Nasdaq is therefore a good candidate to be replaced with a mock object.

#include "nasdaq.h" 
struct Trader {
  void stopLoss(std::string symbol,
                unsigned int amount,
                Price lowerLimit) {
    Share share = exchange.lookupBy(symbol);
    // causes network call
    Price currPrice = share.currentPrice();
    if (currPrice < lowerLimit)
       exchange.sell(share, amount);
    // dito
    }
  }
private: 
  Nasdaq exchange; 
};
			
Listing 1

One of the many possibilities in C++ to inject dependencies from outside is to extract a template parameter and to inject the dependency at compile time making use of parametric polymorphism. We therefore call this seam type compile seam . After applying the refactoring extract template parameter , the code results as shown in Listing 2.

#include "nasdaq.h" 
template<typename STOCKEXCHANGE=Nasdaq>
struct TraderT {
  void stopLoss(std::string symbol, 
     unsigned int amount, Price lowerLimit) {
    // as before
  }
private:
  STOCKEXCHANGE exchange;
};
typedef TraderT<> Trader;
			
Listing 2

This code has a seam because we now have an enabling point: the place where the template class TraderT is instantiated. Note that we create a typedef which instantiates the template with the concrete type that has been used before applying the refactoring (here through the use of a default template parameter). This has the advantage that we do not break existing code that still wants to use Nasdaq .

We now give a complete example of how we think mocking objects should be done in C++ and explain the internals of our approach in the subsequent sections of this article. We apply the classic unit test work flow proposed by the xUnit pattern [ Meszaros07 ] in Listing 3: setup, exercise, verify and teardown (whereas the latter is not necessary here).

#include "mockobjects.h" 
#include <cassert>
void test_sell_shares_when_current_price_below_stop_loss_limit() {
  // setup
  static std::vector<calls> allCalls{1};
  struct MockExchange {
    MockExchange() :
       mockid{reserveNextCallId(allCalls)} {
      allCalls[mockid].pushback
         (call{"MockExchange()"});
    }
    Share lookupBy(std::string symbol) {
      allCalls[mockid].pushback
         (call{"lookupBy(std::string)", symbol});
      return {Share{symbol, Price{29}};
    }
    void sell(Share share, unsigned int amount) {
      allCalls[mockid].pushback
         (call{"sell(Share, unsigned int)",
                     share, amount});
    }
    const size_t mockid;
  }; 
  // exercise
  TraderT<MockExchange> trader;
  trader.stopLoss("FB", 1000000, Price{30});
  // verify
  calls expected = {
    {"MockExchange()"},
    {"lookupBy(std::string)", "FB"},
    {"sell(Share, unsigned int)", "FB", 1000000}
  };
  assert(expected == allCalls[1]);
}
			
Listing 3

We use the vector allCalls to register all function calls the SUT makes on the injected local class MockExchange . It needs to be defined static because of the shortcomings local classes still have with C++11. We create the vector initially with a size of one. Index 0 is reserved for calls of static member functions on the mock object. Note that every mock object has a mock ID which is used to access the calls made by the SUT on a specific instance of the mock object class. In the registrations of the function calls, we use this to store the call for the corresponding mock object instance. Also note the usage of C++11’s new initialiser lists for specifying our expectations. At the end of our example, we assert the calls made with the index 1 (we only have one instance of MockExchange ) with our expectations.

We think it is worthwhile to have the code for the mock object in the unit test without hiding it behind DSL’s built up of macros as other mock object libraries do. This yields more transparency and exploits the full power of the host language when the library does not provide a desired feature. Furthermore, we circumvent the numerous problems that come with the application of macros.

In the classic mock object approach the unit test does not exercise any assertions. This is entirely handled by the mock object which – when called during SUT execution – compares the actual arguments received with the expected arguments using equality assertions and fails the test if they do not match. We have decided against this common approach and exercise the assertions in the unit test itself because we want to be independent of the underlying unit testing framework. We therefore do not assert for equality in the mock object member functions, but instead compare the string traces in the unit test. Also note that our comparisons are order-sensitive and therefore we use strict mock objects [ Meszaros07 ].

Local classes: 2nd class citizens in the C++ world
Local classes are still not first-class citizens even in C++11. Declarations in local classes can only use type names, static and external variables, functions and enums from their enclosing scope. Access to automatic variables is therefore prohibited [ ISO/IEC11 ]. Furthermore, they are also not allowed to have static and template members.

How to record function calls

An important part of a mock object implementation is the recognition of the function calls the SUT makes on the mock object while a unit test runs. Beside the sequence and number of calls, we are also often interested in their argument values. We therefore have to store these facts to be able to later compare the calls with the users expectations.

We use an abstraction named call for this purpose which represents a call of a function. Its basic functionality is shown in Listing 4.

// mockobjects.h
#include <sstream>
#include <vector>
struct call {
  template<typename ...Param> 
  call(std::string const& funSig,
       Param const& ...params) {
       record(funSig, params ...);
  }
  template<typename Head, typename ...Tail>
  void record(Head const& head,
            Tail const& ...tail)  {
    std::ostringstream oss;
    toStream(oss, head);
    if (sizeof...(tail)) {
      oss « ",";
    }
    trace.append(oss.str()); 
    record(tail ...);
  }
  void record() { }
  std::string trace;
}; 
typedef std::vector<call> calls;
			
Listing 4

A function call consists of the signature of the function and its argument values. Because we have to allow arguments of any type, we use a template parameter for the arguments in the constructor of call . Due to the fact that we do not want to restrict the number of arguments, we use a variadic template parameter pack.

The constructor of call uses the variadic template member function record to recursively process the arguments of the function call. record(Head const&, Tail const&) is used as the recursion step whereas record() handles the basic case of the recursion. Note the use of template parameter unpacking in the sizeof call to separate the argument values with commas and for the recursive call in record.

call uses a std::string object to store the function signature and the argument values. This is used to remember the values of any possible argument type and to give the user as much information as possible when a comparison fails. Also note the typedef calls which we use to store the calls on an instance of a mock object class.

What are variadic templates?
Variadic templates – introduced with C++11 – basically address two limitations we have with the old C++ standard: the impossibility to instantiate class and function templates with arbitrary long parameter lists and to pass any number of arguments to a function in a type-safe manner. The ellipsis used on the left side of the parameter in variadic templates denote a so called template parameter pack which groups zero or more template arguments. The inverse action of packing is called unpacking and is applied when the ... operator is used on the right side of a template or function call argument. Variadic templates are often used in combination with recursion which we also apply in this article.

Requirements on function parameter types

To store the argument values in a string, we expect that types used for the function arguments implement a corresponding operator(ostream&, Type) . To prevent cryptic compiler errors if this is not the case, we use some template meta programming tricks taken from the Boost exception library. The interested reader might want to have a look at the file is_output_streamable.hpp in a recent Boost library version to see how this works. This is done in the function toStream which delegates the work of using the stream output operator in case it is defined and otherwise writing a message into the stream to inform the user about the missing operator.

  template<typename T>
  std::ostream& toStream(std::ostream& os,
                         T const& t) {
    selectbuiltinshiftif<T,
       isoutputstreamable<T>::value> out(os);
    return out(t);
  }

Specifying expectations with initialiser lists

When unit testing our objects, we want to compare a list of function calls against our expectations. We use initialiser lists for specifying expectations the SUT has to fulfil. Note that C++ always allowed initialisation of plain old data (POD) types and arrays with initialiser lists, i.e., to give a list of arguments in curly brackets. But it was not possible in the old standard to use initialiser lists with regular (non-POD) classes. This has changed with C++11 where we are now able to instantiate regular classes with initialiser lists. This can be seen here with our calls vector.

  calls expected = {
    {"foo(int i)", 42},
    {"bar(char c)", 'x'},
    {"foo(std::string s, double d)",
          "mockator", 3.1415}
  };

In order to make comparisons work between the actual executed calls of the SUT on the mock object and our expectations, we have to provide an equality operator for call . operator== just delegates the work to the equality operator of std::string to compare the traced function call. To allow unit testing frameworks to print a string representation of the object under consideration if a comparison fails, we also provide a stream operator for call .

  bool operator==(call const& lhs, 
                  call const& rhs) {
    return lhs.trace == rhs.trace;
  }
  std::ostream& operator«(std::ostream& os,
                          call const& c) {
    return os « c.trace;  
  }

Another important thing to explain is the function reserveNextCallId applied in Listing 3. This function is used to initialise the ID of the mock object and to add another call vector to the allCalls vector which collects all calls made on all instances of the mock object class. Its implementation is:

  size_t reserveNextCallId
     (std::vector<calls> &allCalls) {
    size_t counter = allCalls.size();
    allCalls.pushback(calls{});
    return counter;
  }

Reference implementation

Based on the mock object library discussed in this article, we have implemented Mockator. Mockator is a plug-in for the Eclipse C/C++ Development Tooling (CDT) platform including a header-only C++ based mock object library. The library also supports order-independent comparisons, the use of regular expressions in the expectations, nice string representations for easier comparisons of STL containers when used as function arguments and C++03 beside C++11.

Because common mock object libraries often lack good IDE support, we implemented a plug-in for Eclipse CDT that – beside its support for seams presented in the foregoing article –recognises missing member functions the SUT calls on the mock object and is able to generate them including the presented call registrations. It is able to generate code for both C++ standards and not only supports the common mock objects based on inheritance, but also ones based on parametric polymorphism.

We recognised that it is often beneficial to just mock a single function instead of extracting an interface or a template parameter for classes. Therefore, we also implemented mocking of functions. Additionally, we provide various convenience functions to make working with mock objects easier like moving them to a namespace (useful if the unit test gets too big because of the mock object code and to share mock objects between unit tests), converting fake to mock objects, toggling the call recording on a member function level and recognising inconsistent expectations. The interested reader can download Mockator and give it a try. It is available as an alpha version under [ Rüegg12 ].

References

[Feathers04] Working Effectively With Legacy Code , Michael C. Feathers 2004

[ISO/IEC11] Working Draft, Standard for Programming Language C++, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf , February 2011

[Meszaros07] Gerard Meszaros, Unit Test Patterns: Refactoring Test Code , Addison-Wesley 2007

[Rüegg12] Michael Rüegg, ‘Mockator’, available from http://www.mockator.com , 2012






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.