Stubs and Mocking

Steven J Zeil

Last modified: Feb 27, 2014

Contents:
1. Stubs
2. Mock Objects
2.1 Mock Frameworks

1. Stubs


The Lowly Stub


Where ADTs and Stubs Meet

In some ways, ADTs and OOP make it easier to do stubs.

A mock object is an object whose interface emulates a “real” class for testing purposes.

Mock objects may …


Example: Mailing List

In our MailingList tests, we relied on a Contact class.

Here is the real interface for Contact:

contactnmad.h
#ifndef CONTACT_H
#define CONTACT_H

#include <iostream>
#include <string>

#include "name.h"
#include "address.h"

class Contact {
  Name theName;
  Address theAddress;

public:
  Contact (Name nm, Address addr)
    : theName(nm), theAddress(addr)
  {}


  Name getName() const   {return theName;}
  void setName (Name nm) {theName= nm;}

  Address getAddress() const     {return theAddress;}
  void setAddress (Address addr) {theAddress = addr;}

  bool operator== (const Contact& right) const
  {
    return theName == right.theName
      && theAddress == right.theAddress;
  }

  bool operator< (const Contact& right) const
  {
    return (theName < right.theName)
      || (theName == right.theName
      && theAddress < right.theAddress);
  }
};

inline
std::ostream& operator<< (std::ostream& out, const Contact& c)
{
  out << c.getName() << " @ " << c.getAddress();
  return out;
}


#endif

#ifndef NAME_H
#define NAME_H

#include <iostream>
#include <string>

struct Name {
  std::string last;
  std::string first;
  std::string middle;


  Name (std::string lastName,
       std::string firstName,
       std::string middleName)
    : last(lastName), first(firstName),
      middle(middleName)
    {}

  bool operator== (const Name& right) const;
  bool operator< (const Name& right) const;

};

std::ostream& operator<< (std::ostream& out, const Name& addr);


#endif


#ifndef ADDRESS_H
#define ADDRESS_H

#include <iostream>
#include <string>

struct Address {
  std::string street1;
  std::string street2;
  std::string city;
  std::string state;
  std::string zipcode;

  Address (std::string str1,
       std::string str2,
       std::string cty,
       std::string stte,
       std::string zip)
    : street1(str1), street2(str2),
      city(cty), state(stte), zipcode(zip)
    {}

  bool operator== (const Address& right) const;
  bool operator< (const Address& right) const;

};

std::ostream& operator<< (std::ostream& out, const Address& addr);


#endif


What Do We Need for the MailingList Test?

But in our tests, the only things we needed to do with contacts was


A Mock Contact

So we made do with a simpler interface that

contact.h
#ifndef CONTACT_H
#define CONTACT_H

#include <iostream>
#include <string>

typedef std::string Name;
typedef std::string Address;

class Contact {
  Name theName;
  Address theAddress;

public:
  Contact() {}

  Contact (Name nm, Address addr)
    : theName(nm), theAddress(addr)
  {}


  Name getName() const   {return theName;}
  void setName (Name nm) {theName= nm;}

  Address getAddress() const     {return theAddress;}
  void setAddress (Address addr) {theAddress = addr;}

  bool operator== (const Contact& right) const
  {
    return theName == right.theName
      && theAddress == right.theAddress;
  }

  bool operator< (const Contact& right) const
  {
    return (theName < right.theName)
      || (theName == right.theName
          && theAddress < right.theAddress);
  }
};

inline
std::ostream& operator<< (std::ostream& out, const Contact& c)
{
  out << c.getName() << " @ " << c.getAddress();
  return out;
}


#endif

Replacing the Name and Address classes by simple strings.

2. Mock Objects

Object-oriented languages make it easier to create some mock objects

Example: for an application that drew graphics using the Java java.awt.Graphics API, I created the following as a mock class:

GraphicsStub.java
package Pictures;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.image.ImageObserver;
import java.text.AttributedCharacterIterator;

/**
 * Special version of Graphics for testing that simply writes all graphics requests to
 * a string.
 * 
 * @author zeil
 *
 */
public class GraphicsStub extends Graphics {

    private StringBuffer log;

    public GraphicsStub() {
        log = new StringBuffer();
    }

    public void clearRect(int arg0, int arg1, int arg2, int arg3) {
        entering("clearRect",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void clipRect(int arg0, int arg1, int arg2, int arg3) {
        entering("clipRect",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void copyArea(int arg0, int arg1, int arg2, int arg3, int arg4,
            int arg5) {
        entering("copyArea",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
    }

    public Graphics create() {
        entering("create", "");
        return new GraphicsStub();
    }

    public void dispose() {
        entering("dispose", "");
    }

    public void drawArc(int arg0, int arg1, int arg2, int arg3, int arg4,
            int arg5) {
        entering("drawArc",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, ImageObserver arg3) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
        return true;
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, Color arg3,
            ImageObserver arg4) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
        return true;
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, int arg3,
            int arg4, ImageObserver arg5) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4);
        return true;
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, int arg3,
            int arg4, Color arg5, ImageObserver arg6) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
        return true;
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, int arg3,
            int arg4, int arg5, int arg6, int arg7, int arg8, ImageObserver arg9) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5
                + arg6 + ":" + arg7 + ":" + arg8);
        return true;
    }

    public boolean drawImage(Image arg0, int arg1, int arg2, int arg3,
            int arg4, int arg5, int arg6, int arg7, int arg8, Color arg9,
            ImageObserver arg10) {
        entering("drawImage",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5
                + arg6 + ":" + arg7 + ":" + arg8 + ":" + arg9);
        return true;
    }

    public void drawLine(int arg0, int arg1, int arg2, int arg3) {
        entering("drawLine",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void drawOval(int arg0, int arg1, int arg2, int arg3) {
        entering("drawOval",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void drawPolygon(int[] arg0, int[] arg1, int arg2) {
        entering("drawPolygon",
                "" + arg0 + ":" + arg1 + ":" + arg2);
    }

    public void drawPolyline(int[] arg0, int[] arg1, int arg2) {
        entering("drawPolyline",
                "" + arg0 + ":" + arg1 + ":" + arg2);
    }

    public void drawRoundRect(int arg0, int arg1, int arg2, int arg3, int arg4,
            int arg5) {
        entering("drawRoundRect",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
    }

    public void drawString(String arg0, int arg1, int arg2) {
        entering("drawString",
                "" + arg0 + ":" + arg1 + ":" + arg2);
    }

    public void drawString(AttributedCharacterIterator arg0, int arg1, int arg2) {
        entering("drawString",
                "" + arg0 + ":" + arg1 + ":" + arg2);
    }

    public void fillArc(int arg0, int arg1, int arg2, int arg3, int arg4,
            int arg5) {
        entering("fillArc",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
    }

    public void fillOval(int arg0, int arg1, int arg2, int arg3) {
        entering("fillOval",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void fillPolygon(int[] arg0, int[] arg1, int arg2) {
        entering("fillPolygon",
                "" + arg0 + ":" + arg1 + ":" + arg2);
    }

    public void fillRect(int arg0, int arg1, int arg2, int arg3) {
        entering("fillRect",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void fillRoundRect(int arg0, int arg1, int arg2, int arg3, int arg4,
            int arg5) {
        entering("fillRoundRect",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5);
    }

    private Shape clip = null;
    public Shape getClip() {
        return clip;
    }

    public Rectangle getClipBounds() {
        return null;
    }

    private Color color;
    public Color getColor() {
        return color;
    }

    public Font getFont() {
        return null;
    }

    public FontMetrics getFontMetrics(Font arg0) {
        return null;
    }

    public void setClip(Shape arg0) {
        entering("setClip",
                "" + arg0);
        clip = arg0;
    }

    public void setClip(int arg0, int arg1, int arg2, int arg3) {
        entering("setClip",
                "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3);
    }

    public void setColor(Color arg0) {
        entering("setColor",
                "" + arg0);
        color = arg0;
    }

    public void setFont(Font arg0) {
        entering("setFont",
                "" + arg0);
    }

    public void setPaintMode() {
        entering("setPaintMode", "");
    }

    public void setXORMode(Color arg0) {
        entering("setXORMode",
                "" + arg0);
    }

    public void translate(int arg0, int arg1) {
        entering("translate",
                "" + arg0 + ":" + arg1);
    }

    public String toString()
    {
        return log.toString();
    }

    public void clear()
    {
        log = new StringBuffer();
    }

    private void entering(String functionName, String args)
    {
        log.append(functionName);
        log.append(": ");
        log.append(args);
        log.append("\n");
    }

}

It basically writes each successive graphics call into a string, that can later be examined by unit test cases.


OO Mock Example

Here is a test using that mock:

linesegTest.java
package PictureTests;
import java.awt.Color;
import java.awt.Point;
import java.lang.reflect.Method;
import java.util.Scanner;

import junit.framework.*;
import junit.textui.TestRunner;
import Pictures.GraphicsStub;
import Pictures.LineSegment;
import Pictures.Shape;


/**
 * Test of the LineSegment class
 */
public class LineSegmentTest extends TestCase {
    ⋮
    public void testPlot()
    {
        Point p1 = new Point(22, 341);
        Point p2 = new Point(104, 106);
        LineSegment ls = new LineSegment(p1, p2, Color.blue, Color.red);
        
        GraphicsStub g = new GraphicsStub();
        GraphicsStub gblue = new GraphicsStub();
        gblue.setColor(Color.blue);
        GraphicsStub gline1 = new GraphicsStub();
        gline1.drawLine(22, 341, 104, 106);
        GraphicsStub gline2 = new GraphicsStub();
        gline2.drawLine(104, 106, 22, 341);

        ls.plot(g);
        assertTrue (g.toString().contains(gblue.toString()));
        assertTrue (g.toString().contains(gline1.toString()) 
                    || g.toString().contains(gline2.toString()));
    }
    ⋮

2.1 Mock Frameworks

Increasingly, unit test frameworks are including special support for mock object creation.


Mocking Up an Example

Suppose that we are writing an application that uses this class:

class Turtle {
  ⋮
  virtual ~Turtle() {}
  virtual void PenUp() = 0;
  virtual void PenDown() = 0;
  virtual void Forward(int distance) = 0;
  virtual void Turn(int degrees) = 0;
  virtual void GoTo(int x, int y) = 0;
  virtual int GetX() const = 0;
  virtual int GetY() const = 0;
};

To set up the test of the application code that uses this, we derive a mock class that has the same interface…


A Mock Class

#include "gmock/gmock.h"
class MockTurtle : public Turtle {
 public:
  ...
  MOCK_METHOD0(PenUp, void());
  MOCK_METHOD0(PenDown, void());
  MOCK_METHOD1(Forward, void(int distance));
  MOCK_METHOD1(Turn, void(int degrees));
  MOCK_METHOD2(GoTo, void(int x, int y));
  MOCK_CONST_METHOD0(GetX, int());
  MOCK_CONST_METHOD0(GetY, int());
};

Using the Mock

Then in unit tests for the application class, we

  1. use instances of the mock class
  2. Write assertions that describe how we expect the stub to be exercised.
#include "mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast;

TEST(DrawACircle, CanDrawSomething) {
  MockTurtle turtle;
  EXPECT_CALL(turtle, PenDown())
      .Times(AtLeast(1));
  Painter painter(&turtle);
  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}

So, if we call painter.drawCircle(...), we


Stubs can Generate Output

EXPECT_CALL(g, foo(_))
    .TIMES(3)
    .WillRepeatedly(Return(150));
EXPECT_CALL(g, foo(1))
    .WillOnce(Return(100));
EXPECT_CALL(g, foo(2))
    .WillOnce(Return(200));
 

This assertion says


Are Mock Frameworks Useful?