Unit Testing Deep Dive: What are Stubs, Mocks, Spies and Dummies?

Unit Testing Deep Dive: What are Stubs, Mocks, Spies and Dummies?

Understanding the 4 core tools you’ll use on all your unit tests

You might hate or love unit tests, that’s up to you, but the fact of the matter is that if you don’t understand the concepts behind them, you’ll be as efficient at writing test as I am at cooking and keeping a clean kitchen at the same time (spoiler alert: I make a mess every time).

The first core step to be great at writing unit tests is to understand their focus. Unit tests aren’t integration tests, and they have to test a single unit of code, whatever that means. I’m going to assume you already know that part, and if you don’t please check out this article I wrote about best practices to follow, it’ll give you a head start.

With that out of the way, let’s take a look at 4 tools you’ll be using while writing your unit tests. And I don’t mean your IDE or any plug-ins or extensions, I’m rather talking about conceptual tools: stubs, mocks, spies and dummies.

Let’s get cracking!

What are Stubs?

Remember when I said unit tests weren’t integration tests? Well, I meant it!

Often times I see developers writing tests for code that interacts with a database by booting up a “test database” where the test can trigger a write (for example) and validate it by querying the DB. That’s so wrong I’d have to dedicate a whole article to explain it in detail, so take my word for it in the meantime.

Stubs help you deal with these situations where your code is interacting with 3rd party services. Be it a database, an API or even a file on your hard drive, stubs provide the code that uses the services with a simpler version.

This new version (the stub) instead, returns a known and controlled value. For instance, if you’re testing a function that writes a value to the database, you should write a stub that avoids the db interaction but returns a successful result.

Through that one you’ll be able to test what happens when the write operation works. You can then write another stub (in another test) that returns a failed result, thus allowing you to test the part of your logic that deals with error handling.

You can stub a function or a method in a particular object (as long as the language allows for it).

So let’s take a quick look at an example.

/// the function to test
function saveUser(usrData, dbConn) {

  let q = createQueryFromUser(usrData)
  let result = dbConn.query(q)

  return result;
}


//the stub
makeStub(dbConn, 'query', () => {
  return true;
})

//the test
it("should return TRUE when the query succeeds", () => {
  let result = saveUser({
    name: "Fernando",
    password: "1234"
  }, dbConn)
  result.should.be.true
})

The above example has a few things to unpack, also notice that while the example is written in pseudo-JavaScript, the concepts can be extrapolated to all languages.

First is the function to be tested, right now it’s a simple function that receives data, a database connection object and relies on a fake createQueryFromUser function to create the actual SQL query. The method query from the dbConn object is the one that interacts with the database, and the one we’re interested in stubbing since we don’t want the query to actually fire.

Here is where the stub comes into play, the makeStub function takes care of magically overwriting the method query from our database connection with the anonymous function we’re passing (which is a dummy function that only returns TRUE every time).

Finally, the actual unit test is making use of the stub (because it was defined before). This test makes sure that our function returns the right boolean value when things go right.

This is but one example where you can benefit from stubs. To be honest, any time where you have a function with a dynamic outcome, you’ll have to find a way to ensure the same result on every execution of the test. And for that you’ll use stubs.

What are Mocks?

Mocks are like the twin brothers to Stubs, they look a lot like each other and people often confuse them. However, they’re two very different individuals…err, or rather, tools you can use on your tests.

When stubs allowed you to replace or redefine a function or a method, Mocks allow you to set expected behaviors on real objects/functions. So you’re not technically replacing the object or function, you’re just telling it what to do in some very specific cases, other than that, the object remains working as usual.

Let’s look at an example to understand the definition: imagine having to test an aisle replenishment function. It takes items from the inventory and puts them on the right aisle. The key to test here is that every time we replenish an aisle, the same amount of elements need to be taken from the inventory as well.

let inventory = createMock(Inventory("groceries"))
//set expectations
inventory.expect("getItems", 10).returns(TRUE).expect("removeFromInventory", 10).returns(TRUE)

let aisle = Aisle("groceries")
aisle.replenish(10, inventory) //executes the normal flow
assertion(aisle.isFull(), "equals to", TRUE)

So the expectation here would be that every time we call the method that deals with the aisle’s content, it also calls the one that deals with the general inventory and removes the same amount of elements.

Keep in mind that in some cases the expected behavior for mocks is automatically checked by whatever framework you’re using. Reason why there is no real assertion dealing with the expectations, if they were not met, the mock would’ve thrown an exception and the test would not have passed.

And in this particular case, the expectation says that the getItems method will be called with a 10 as its attribute, it’ll return TRUE and that it’ll also call the removeFromInventory function, with a 10 as its attribute as well. Ending all on a TRUE being returned.

Of course, we could’ve done this using stubs, but that’s not the point, in many situations these tools can be used for the same or similar use cases.


If you liked what you’ve read so far, consider subscribing to my FREE newsletter “The rambling of an old developer” and get regular advice about the IT industry directly in your inbox


What on earth are Spies?

No, no no, I’m not talking about 007-type spies, we’re still talking about unit tests here — sorry to disappoint.

Spies, as the name suggests, allow us to understand what happens inside the tested code, even when we don’t really have access to it. It sounds shifty, I know, but it has its uses.

In other words, spies are stubs that gather execution information, so they can tell you, in the end, what got called, when and with which parameters.

Think about the example from the mocks above, we had to set the expectations beforehand to make sure that everything we wanted would be executed. We could check for the same thing with spies by “spying” on the inventory and asking if those methods were actually called and with which parameters.

Let’s check out another example this time, a file reader function that should also close the file handler once it’s done with it.

const filename = "yourfile.txt"
let myspy = new Spy(IOModule, "closeFile") //create a spy for the method closeFile in the module dedicated to I/

function readConfigFile(fname) {
 const reader = new FileReader(filename, IOModule)
 let content = reader.read()
 loadConfig(content)
 IOModule.closeFile(reader);
}


//The test

it("should call the 'closeFile' method after reading the content of the file", () => {
  readConfigFile(filename)
  assertion(myspy.called, "equals to", TRUE)  
})
`

The function to be tested is called readConfigFile , it is meant to read a file and load its content as configuration by calling the loadConfig method. As part of our test, we’re interested in understanding if the function actually closes the file handler.

Bear in mind that this test is going against what I said above because it’s actually opening and reading the file, which is a 3rd party dependency our unit tests should not have. To make this test fully “compliant” we would have to also add a stub for our IOModule and control when we’re interested in testing a successful read and a failed one.

Note: Spies, unlike stubs, wrap the target method/function instead of replacing it, so the original code of your target will also be executed.

What are Dummies?

Finally, the last tool I want to cover is the famously useless “dummies”. Dummies, as the name implies, are simple objects that serve no real purpose other than being there when they’re required. Their purpose is to be there when the syntax requires it.

For example, imagine having to call a function that takes 3 arguments, with the first one being another function (an external dependency). Given the current stub for that function, you know that the other 2 attributes won’t be used, however, the interpreter/compiler is complaining that you’re missing the last 2 attributes of the function, so you need to add them.

How can you do that?

You guessed it, through dummies. You’ll just add 2 dummy objects that do nothing but are accepted by the compiler.

Dummies make more sense when they’re used inside strongly typed languages because these types of checks are more common there. For example, take a look at the following TypeScript example:

type UserData = {
  name: string;
  password: string
}

//The function to be tested
function saveUser(usrData: UserData, dbConn: DataBase, validators:DataValidators) {

  if(!validators.validateUserData(usrData)) {
    return false;
  }
  let query = createQueryFromData(usrData);
  let result = dbConn.query(query);
  return result;
}

// The test itself

//the stub
const stubbedValidators: DataValidators = {
  validateUserData: (data: UserData) => false;
}

//the dummies
const userData: UserData = {name: "", password: ""}
const dbConn: DataBase = {}

//the test
it("should return false if the user data is not valid", () => {
  let result = saveUser(userData, dbConn, stubbedValidators);
  result.should.be.false;
})
`

The code defines a new saveUser function which also takes a validators dependency. We’ve also added a validation step, to make sure the data we’re trying to save is “valid” (whatever that means).

But the intention of our test is to make sure that if the data is not valid, we’re returning false . That means we’re not really performing any validation, in fact, we need to stub that validator to control the result, otherwise if tomorrow our validation routine were to change, we might now be passing a valid data sample and the test would fail.

The problem now is that by looking at our business logic if the data is not valid, we’re not really using the database connection, nor the actual user data. We need them to be there, but we don’t really need them. So they’ve effectively become dummies.

This is why I’m just passing fake empty objects (A.K.A dummies) as the first 2 attributes of the function.

Stubs, Mocks, Spies and Dummies are the bread and butter of everything you’ll do on your tests, the more you use them, the more familiar they’ll feel and the easier it’ll be for you to understand how to tackle a new test.

Where the examples clear enough? Do you still have questions about the examples? Leave a comment and we’ll talk!

Did you find this article valuable?

Support The Rambling of an Old Developer by becoming a sponsor. Any amount is appreciated!