Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The Four Horsemen of the Test Suite

DZone's Guide to

The Four Horsemen of the Test Suite

Learn to understand stubs, mocks, spies, and dummies as they are used in software testing suites and examples of how they are useful.

· Performance Zone ·
Free Resource

Sensu is an open source monitoring event pipeline. Try it today.

Also known as stubs, mocks, spies, and dummies.

Now, on a more serious note, they are all similar tools, so I wanted to cover them as part of the same section since they’re all related in one way or another. It’s important to note that usually production systems aren’t straightforward and your methods and functions will normally interact with each other and other external services (such as APIs, Databases, even the filesystem) so this set of particular tools helps you out in that aspect.

One key mantra that you need to repeat over and over when writing tests is: "I shall not test code that’s already been tested by others." And even though in theory it’s quite obvious (I mean, why would you? Really?), in practice, the line is sometimes a little blurry. One very common case, specially when writing public APIs, is to use databases. Your CRUD methods, for instance, will most likely be 80% database interaction, so should you test that code? The answer is “not entirely.” Let me give you an example:

function savePerson(person) {
    if (validationFunction(person)) {
        query = createSavePersonQuery(person)
        return executeQuery(query)
    } else {
        return false
    }
}

The above code shows a very basic database interaction, you have several functions there that you probably already tested individually because of their complexity (validationFunction and createSavePersonQuery) and you also have a function called executeQuery, that in our case is provided by your database library. You didn’t write that function, you don’t even have access to it’s code, why would you then, care about testing it? You can’t really do anything about it if it fails. More so, why would you even consider depending on your database server being up and running? Are you going to be using the production database for your tests? What will you do with the garbage data generated by your tests? What if your database server is up, but your table is not created? Will the test fail? Should it?

These are all normal questions that arise when starting to write tests and hitting the brick wall that is reality. If you’re not just starting with tests, but with software development in general you might think that the right way to go is to have a “test database”, one you can control and you can do whatever you want with, heck! I’ve done it, it’s completely normal but also, wrong.

You see, when you add an external service into your tests, even one you think you can control such as your own database server, you’re implicitly testing that service and the connectivity between both systems inside your unit test. You’ve turned a simple and straightforward test into a very complex one that is not even prepared to handle everything that could go wrong. What if your network fails? Should this test fail? What if you forgot to start your db server? Should this test fail too? See where I’m going with this? And I’m just giving you one simple example, one database, I’m not covering logging, other APIs, multiple database queries, and so forth. You definitely need to cut all connections to the outside when unit testing, and that means everything that is not your target unit of code. Fear not though, because here is where this particular set of tools comes into play.

Stubs

Stubs help you deal with external services, by replacing the code that uses them, with a simpler version, that instead, returns a known and controlled value.You can stub a function or a method in a particular object (as long as the language lets you), so instead of controlling the database and its content (like in the previous example), you would overwrite the function that does the actual query with one that controls the output to whatever you need it to be. This way you can safely test all possible cases (including those when the network connectivity fails, or the database is down).

Statement: when the person is saved, the
function should
return TRUE
Stub: executeQuery(q) {
    return TRUE
} //we assume the query execution went well
var person = {
    name: “Fernando Doglio”,
    age: 34
}
assertion(savePerson(person), “equals to”, TRUE)

Statement: when the person’ s data is not valid, the
function should
return FALSE
Stub: validationFunction(data) {
    return FALSE
}
var person = {
    name: “Fernando Doglio”,
    age: 34
}
assertion(savePerson(person), “equals to”, FALSE)

This shows two examples of why stubs are so useful, the first one shows how you can easily control the outcome of the interaction with an external service, you don’t need complex logic inside your stubs, the important part on them is their returned value. The second example is not overwriting an external service, but rather, an external function, in fact, one that you probably wrote. And the reason for that (instead of simply providing an invalid person object as input) is that in the future, your validation code could change, maybe you added or remove valid parameters from your person definition, and now your test could fail, not due to the code you’re testing, but to an unwanted side effect. So instead of suffering from that, you simply eliminate the dependency on that function, and make sure that no matter what happens to the internal logic of validationFunction, you’ll always handle the FALSE output correctly.In fact, both examples from above show the two most common uses for stubs:

  1. Removing dependency from external service

  2. Forcing a logical path inside your target test code

Mocks

Mocks are very similar to stubs, so much so in fact, that many people use both terms to refer to the same behavior. But that is not correct, even though they’re both conceptually similar, they are also, different.When stubs allowed you to replace or redefine a function or a method (or even an entire object, why not?), 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.

Statement: When replenishing the diapers aisle, the same amount added, needs to be removed from the inventory
Code:

    var inventory = Mock(Inventory(“diapers”))
//set expectations
inventory
    .expect(“getItems”, 100)
    .returns(TRUE)
    .expect(“removeFromInventory”, 100)
    .returns(TRUE)

var aisle = Aisle(“diapers”)
aisle.setRequiredItems(100)
aisle.replenish(inventory) //executes the normal flow
assertion(aisle.isFull(), “equals to”, TRUE)
assertion(inventory.verifiedBehavior, “equals to”, TRUE)

I know, I know, two assertions inside the same test case, I haven’t even finished the post and I’m already going against my words. Bare with me here, in some cases the expected behavior for mocks is automatically checked by whatever framework you’re using, so this example is just to let you know it’s happening, now get off my back!

Now, back to the example, we could’ve definitely done this with stubs too, but we’re conceptually testing something different. Not just the final state of the aisle object, but also the way that the aisle object interacts with the inventory, which is a bit harder to do with stubs. During the first part of the test, where we set the expectations, we’re basically telling the mocked object, that its getItems method should be called with a 100 as a parameter, and that when it happens, it should return TRUE. We’re also telling it that its removeFromInventory method should be called with a 100 as parameter and to return TRUE when this happens. In the end, we’re just checking to see if that actually happened.

Spies

As cool as this name might sound to you, we’re still dealing with special objects for your test cases. This particular type of object is an evolution of the stubs. And the reason why I’m just mentioning it is because spies are the answer to the example on the mock section. In other words, spies are stubs that gather execution information, so they can tell you, at the end, what got called, when and with which parameters. There is not much to them, we can look at another example where you’d need to know information about the execution of a function, in order to show you how you could test it with spies.

Statement: FileReader should close the open file after it’ s done.

Code:

    var filename = “yourfile.txt”
var myspy = new Spy(IOModule, “closeFile”) //create a spy for the method closeFile in the module dedicated to I/O
var reader = new FileReader(filename, IOModule)
reader.read()
assertion(myspy.called, “equals to”, TRUE)

The example above should probably be one of many tests for the FileReader module, but it exemplifies when a spy can come in handy.Note: The spy unlike the stub, wraps the target method/function, instead of replacing it, so the original code of your target will also be executed.

Dummies

Dummies are simply objects that serve no real purpose other than being there when they’re required. They are never really used, but in some cases, such as strongly typed languages, you might need to create dummy objects for your method calls to be possible.If you’re creating a stub of a method that receives three parameters, even though you’re not thinking about using them, you might need to create them so they can be eventually passed to it. This is a very simple case of test utility object, but it’s a name that also gets mentioned quite a bit, so I thought I’d cover it.

Well, did you enjoy it? This is just a portion of one of the new chapters for the second edition of "REST API Development With Node.js," in this case, a full chapter on testing. If you did enjoy it and would like to read more about testing and development in general, please feel free to visit my site.

Thanks for reading!

Sensu: workflow automation for monitoring. Learn more—download the whitepaper.

Topics:
unit testing ,performance ,testing ,mocks

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}