GithubHelp home page GithubHelp logo

mockcpp's Introduction

Mock++: A Portable Mocking Framework for C++

The Mock++ framework provides portable mocking support for unit testing C++ applications. This framework makes use of concepts available in C++11, such as variadic templates and tuples, and combines these with simple macros to make the creation and use of mock objects as easy as possible in a language that lacks runtime reflection.

Other frameworks may take advantage of non-portable vtable implementation details, but this framework should work with any compiler that is compliant with the C++11 standard. Using this framework does not require a migration to C++11, however, as code can be written in C++98 or whichever subsets one chooses to use. Only the unit tests themselves must make use of these features.

Usage Example

The following example illustrates the use of this library. We can mock any virtual method that can be overridden by a derived type, but for sake of example, we will create a pure abstract interface.

class Widget
{
public:
    virtual bool isReady() = 0;
    virtual void performAction(int) = 0;
    virtual ~Widget() = 0;
};

Let's assume that this interface is being passed to an object we intend to unit test. We'll call this object A.

class A
{
public:
    ///Construct an A using a Widget.  A owns this widget.
    A(Widget*);

    ///frobulate the widget
    void frobulate();

private:
    std::shared_ptr<Widget> widget_;
};

Now, we can pass a mock Widget to A, but first we need to create a mock of Widget. Unlike reflective languages, there is a bit of scaffolding, but helper macros make this simple.

class MockWidget : public Widget, public mockpp::Mock<Widget>
{
public:

    ~MockWidget() { }

    MOCK_FUNCTION(bool, isReady);
    MOCK_FUNCTION_VOID_ARGS(performAction, int);
};

Within our unit test, we can construct the MockWidget, and control how it works.

    using mockpp;

    auto widget = new MockWidget();

    PROBE(widget, isReady).toReturn(true);
    PROBE(widget, performAction).toDo([](int i) { ASSERT_EQ(i, 1); });
    auto aObj = make_shared<A>(widget); //a takes ownership of widget

    aObj->frobulate();

    //assuming Google Test semantics here.
    ASSERT_TRUE(VALIDATE(widget, isReady).called());
    ASSERT_TRUE(VALIDATE(widget, performAction).called(1));

Mocks have several different selectors, which allow the user to access functionality. The first selector here is PROBE. The probe selector allows us to control behavior of the mock object. In this case, we probe the isReady function to return true when called. By default, a mocked function will return whichever value is considered the default value initialization for a constructed type. In this case, bools return false. Ints return 0. Pointers return nullptr. Next, we probe the performAction function so that it performs the lambda expression we pass in, which asserts that the parameter passed into performAction equals 1. Being able to override the behavior of mocked functions is a powerful feature found in most mocking frameworks. With C++11 lambda expressions, we can do the same thing.

The mock is then passed to the constructor of A. When the frobulate method of A is called, it uses the mock object. We can then validate its use of the mock by using the validate selector. This selector allows us to verify that methods in the mock object were called, and we can even test the values passed to the methods by adding them to the call. The default behavior of the validate selector is to test each invocation in order. For instance, this test would have failed if performAction were called before isReady. Also, the called() method fails if the arguments passed to the selector are invalid. So, if the above assertion were changed to validate(widget).performAction(2).called(), the result would be false, which would cause the Google Test assertion to fail.

Headers

Headers in mockpp are differentiated between class headers and package headers. Class headers always begin with a capital letter, as do the classes they contain. Class headers are always named the same as the type they contain. Package headers are convenient utility headers that contain all classes in a given namespace. Finally, package forwarding headers contain just the forward declarations of classes in a given namespace, which is convenient for handling cyclic resolution issues, or for facilitating a faster compilation on systems that don't support precompiled headers.

mockpp/mockpp.h effectively includes world. This is a quick and easy way to get started.

Strict and Relaxed Evaluation

By default, Mock++ uses strict evaluation of invocations. This means that it cares about the order in which invocations are validated. The reason why strict evaluation is useful is because it is often important to perform operations on an interface in a certain order. For instance, we may wish to assert that the code under unit test empties a container before it adds elements to it. If the calls happen in a different order, then we may not be able to easily test certain invariance rules.

The downside of strict evaluation is that it can make unit tests brittle when the order of evaluation does not matter. Mock++ provides a different mocking interface for relaxing this evaluation mechanism called RelaxedMock. The RelaxedMock template is a plug-in replacement for Mock. Instead of popping invocations off a queue and evaluating them against assertions in the test, it uses the called() matcher as a query, and finds the first result in the queue that matches this query. This item is then popped off the queue. If no matches are found, then the test assertion fails.

class RelaxedMockWidget : public Widget, public mockpp::RelaxedMock<Widget>
{
public:

    ~RelaxedMockWidget() { }

    MOCK_FUNCTION(bool, isReady);
    MOCK_FUNCTION_VOID_ARGS(performAction, int);
}

Here, RelaxedMockWidget can be used in our original test above, and the order of the two assertions at the bottom can be swapped with no ill effect.

Matchers and "Don't Care"

So far, the called() matcher has been demonstrated. This matcher takes arguments that match the arguments of the call to the invoked method. However, sometimes the test does not care about which arguments were passed to the invocation. Other times, there is no way for the unit test to predict the parameters, such as when an ephemeral object is passed to the invoked method. In these cases, more sophisticated matchers are needed.

Under the covers, the called() matcher is builds up a chain of matchers for individual elements. It then uses this chain to match against invocations. The called method is a template, and it will accept alternative arguments. A user can either pass in the argument type called is expecting, which creates an std::equal matcher, or the user can pass in a matcher argument, which can vary from std::greater / std::less to a user-defined lambda.

Consider the following modification to the above example, which ignores the argument passed to performAction. Here, the assertion passes, as expected.

    ASSERT_TRUE(VALIDATE(widget, performAction).called(Ignore()));

It is also beneficial to have the ability to pass in lambda expressions to evaluate the parameter. For instance, this assertion returns true only if the argument for the invocation of performAction is greater than 5.

    ASSERT_TRUE(VALIDATE(widget, performAction).called([](int x) { return x > 5; }));

Future features

Mock++ works now, and I am currently using it to unit test a compiler I am writing. However, there are aspects of this framework which are not easy. For one thing, there is no easy way to understand why assertions fail when using the called matcher. It would be relatively easy to extend the MockValidator and MockProbe to accept a stringified name of the function and type, so that more intelligent error messages could be displayed. Since this requires tighter integration with the unit testing framework, I kept this as a TODO.

I would like to make this framework easier to use and understand. Suggestions about how to do this are welcome. My first priority was to get the framework running, and my second idea was to make it available to others who might need something like this.

mockcpp's People

Contributors

nanolith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

robbenbasten

mockcpp's Issues

Weirdness with reference parameters for mock functions.

The current implementation of mockcpp creates tuples using the exact type of the parameter. In the case of references, the underlying object upon which the reference resolved may no longer exist at the time of validation. In this case, the behavior may be undefined.

Some template metaprogramming needs to be added to ensure that reference types are unwrapped in the tuple. For instance, string& should be unwrapped to string.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.