Advanced TDD With Mocks

Clay Dowling

claydowling.com

I want to make testing easier

I wouldn't mind if testing also became more fashionable

The Problem

class CartItem {
	private:
		string name;
		int quantity;
		double price;
	public:
		const string &getName() const;
		void setName(const string &name);
		int getQuantity() const;
		void setQuantity(int quantity);
		double getPrice() const;
		void setPrice(double price);

		double getItemTotal();
};
				

The Test


TEST_F(CITest, getLineTotal_withQuantityOne_returnsItemPrice)
{
	item.setName(ITEM_NAME);
	item.setPrice(3.14);
	item.setQuantity(1);

	ASSERT_EQ(item.getItemTotal(), 3.14);
}
				

Minimum Effort


double CartItem::getItemTotal() {
	return 3.14;
}
					

Fail More!


TEST_F(CITest, getLineTotal_withDifferentPrice_returnsItemPrice)
{
	item.setName(ITEM_NAME);
	item.setPrice(2.71);
	item.setQuantity(2);

	ASSERT_EQ(item.getItemTotal(), 5.42);
}						
					

100% Test Coverage!


double CartItem::getItemTotal() {
	return quantity * price;
}
					

On To The Cart

The Cart Interface


class Cart {
  public:
    int itemCount();
    void addItem(unique_ptr<CartItem> item);
};
					

Adding Items


TEST_F(CTest, itemCount_afterOneItemAdded_returnsOne) {

	cart.addItem(make_unique<CartItem>(
		CartItem(ITEM_NAME, 1, 3.14)));

	ASSERT_EQ(cart.itemCount(), 1);
}	

void Cart::addItem(unique_ptr<CartItem> item) {
		items.push_back(move(item));
}

A Wild Interface Appears!


class DataResult {

public:
	virtual shared_ptr<vector<string>> getFields() = 0;
	virtual shared_ptr<vector<string>> getNext() = 0;
	virtual bool eof() = 0;
};
					

It Has a Friend


class DataStore {

public:
	virtual void setQuery(string sql) = 0;
	virtual void setParam(string param, string value) = 0;
	virtual void setParam(string param, int value) = 0;
	virtual void setParam(string param, double value) = 0;

	virtual DataResult execute() = 0;
};

Problem: Databases are too slow

Let Us Make Mock

#include "DataResult.h "

class MockDataResult : public DataResult {
public:
	virtual shared_ptr<vector<string>> getFields() override;
	virtual shared_ptr<vector<string>> getNext() override;
	virtual bool eof() override;

	void addNext(int fieldCount, ...);
	void setFields(int fieldCount, ...);

private:
	queue<shared_ptr<vector<string>>> dataset;
	shared_ptr<vector<string>> fields;
};

Mocking The Fields

shared_ptr<vector<string>> MockDataResult::getFields() { 
  return fields; 
}
						  
void MockDataResult::setFields(int fieldCount, ...) {
  vector<string> f;
  va_list args;
  va_start(args, fieldCount);
  for (int i = 0; i < fieldCount; ++i) {
    f.push_back(va_arg(args, const char *));
  }
  va_end(args);
	
  fields = make_shared<vector<string>>(f);
}

Mocking the Rows

void MockDataResult::addNext(int fieldCount, ...) {
	vector<string> row;
	va_list args;
	va_start(args, fieldCount);
	for (int i = 0; i < fieldCount; ++i) {
	  row.push_back(va_arg(args, const char *));
	}
	va_end(args);
  
	dataset.push(make_shared<vector<string>>(row));
}

Mocking the Rows (cont.)


shared_ptr<vector<string>> MockDataResult::getNext() {
  auto row = dataset.front();
  dataset.pop();
  return row;
}

bool MockDataResult::eof() { return dataset.empty(); }
					

Let's Use Our Mock

TEST_F(CITest, inputStream_givenAllFields_populatesCartItem)
{
	result.setFields(3, "name ", "price ", "quantity ");
	result.addNext(3, ITEM_NAME, "3.14 ", "1 ");

	result >> item;

	ASSERT_EQ(item.getName(), ITEM_NAME);
	ASSERT_EQ(item.getPrice(), 3.14);
	ASSERT_EQ(item.getQuantity(), 1);
}					
					

Using the Fields

DataResult& operator>>(DataResult& in, CartItem& item) {

  ⋮

  auto fields = in.getFields();

  for(int i=0; i < fields->size(); ++i) {
    if ("name " == fields->at(i)) name_idx = i;
    if ("price " == fields->at(i)) price_idx = i;
    if ("quantity " == fields->at(i)) quantity_idx = i;
  }

  ⋮

}

Reading a Row

DataResult& operator>>(DataResult& in, CartItem& item) {

  ⋮

  auto row_ptr = in.getNext();
  auto row = *row_ptr;
  item.setName(row[name_idx]);
  item.setQuantity(stoi(row[quantity_idx]));
  item.setPrice(stod(row[price_idx], nullptr));

  return in;
}

Mocking the DataStore

class MockDataStore : public DataStore {
public:
	MockDataStore();
	void setQuery(string sql) override;
	void setParam(string param, string value) override;
	void setParam(string param, int value) override;
	void setParam(string param, double value) override;
	DataResult execute() override;
private:
	map<string, string> string_params;
	map<string, int> int_params;
	map<string, double> double_params;
	bool execute_called;
};

Capture the Query


void MockDataStore::setQuery(string sql) {
  this->sql = sql;
  string_params.clear();
  int_params.clear();
  double_params.clear();
}						
					

Capture Parameters


void MockDataStore::setParam(string param, string value) {
	string_params[param] = value;
}

void MockDataStore::setParam(string param, int value) {
	int_params[param] = value;
}

void MockDataStore::setParam(string param, double value) {
	double_params[param] = value;
}						
					

Verify Parameters

string MockDataStore::getSql() {
	return sql;
}
string MockDataStore::getStringParam(string param) {
	return string_params.at(param);
}
int MockDataStore::getIntParam(string param) {
	return int_params.at(param);
}
double MockDataStore::getDoubleParam(string param) {
	return double_params.at(param);
}
bool MockDataStore::executeWasCalled() {
	return execute_called;
}

Test Saving Items


TEST_F(CITest, outputStream_byDefault_setsQueryParameters) {

	item.setName(ITEM_NAME);
	item.setPrice(3.14);
	item.setQuantity(5);

	store << item;

	ASSERT_EQ(store.getStringParam(":name "), ITEM_NAME);
	ASSERT_EQ(store.getIntParam(":quantity "), 5);
	ASSERT_EQ(store.getDoubleParam(":price "), 3.14);
	ASSERT_TRUE(store.executeWasCalled());
}				
				

Make The Test Pass


DataStore& operator<<(DataStore& out, CartItem& item) {
	out.setParam(":name ", item.getName());
	out.setParam(":price ", item.getPrice());
	out.setParam(":quantity ", item.getQuantity());
	out.execute();
	return out;
}					
				

What Else?

Question about your specific situation?

What do you want to know more about?

Thank You

Clay Dowling

claydowling.com