Lesson 8 of 21
Introduction to Mocks and Stubs
In this talk we learn about mocks and stubs (external link, opens in new tab) using a simple problem of notifying a logger of something that happened.
In this lesson
Roy Osherove explains how to test a call to a third party (external link, opens in new tab) with a string calculator (external link, opens in new tab) that must send each sum to a slow logger, by adding a logger interface (external link, opens in new tab) and a handwritten fake (external link, opens in new tab) logger class. He defines a fake (external link, opens in new tab) as something that looks like something else, a mock (external link, opens in new tab) as a fake you assert against and a stub (external link, opens in new tab) as any other fake, and allows at most one mock per test (external link, opens in new tab). The fake is passed in through constructor injection (external link, opens in new tab), and a test name (external link, opens in new tab) such as add always calls logger shows that a mock object is involved.
Transcript
[00:04] (external link, opens in new tab) Okay, so yesterday was maybe a very simplistic view of the world. Obviously, we were always able to get a return value from our methods. But today we're going to look at a very different very different scenarios. Today we're going to look at the the case of the calling a third party. We actually yesterday we did do the first the first scenario which is returning a value.
[00:35] (external link, opens in new tab) We didn't actually have any tests for state-based tests yet. But we'll al hopefully have some today as well. But today we're going to focus on what happens when you call a third party. So just as a reminder, we have a unit of work (external link, opens in new tab) that starts with a public API and now the requirement will be that there is a call to a third party (external link, opens in new tab), maybe a logger or something like that. And the way that we're going to to test that is
[01:06] (external link, opens in new tab) We're going to have to get around the fact that we don't have control of the third party. For example, if it's a logger that's written by a different team, if it's a logger that's written by a different team, then maybe their code doesn't work yet. Maybe it has a bug. So our test will fail if we actually use their code. Maybe it works very slowly. So our test will be very slow. So we want to get get control over the third party. One way we can do it is to abstract it away and create a mock object (external link, opens in new tab) something that looks exactly like the third party.
[01:40] (external link, opens in new tab) But we do have control over it. And then the next leap of faith is is that we assume that if our mock object got called then the third party got called. Now it is a leap of faith because we are essentially decoupling our system from another system and we cannot really prove that the other system gets called without an integration test (external link, opens in new tab). So this is the next best step which is to assume that if the mock object got called a third party system got called.
[02:13] (external link, opens in new tab) And this is especially true when what we're going to do is to abstract away things either with adapters abstra interfaces and things like that. We have to assume that the system will eventually work. One way to make sure that actually happens is to have both unit tests (external link, opens in new tab) and integration tests at the same time. That means that unit tests are definitely not enough for you to know that your system is working. Unit tests are just a way for you to develop the system.
[02:44] (external link, opens in new tab) They're not a way for you to make sure the system works. To make sure the system works, you have to have integration and acceptance tests. >> There is no other way because at some point things are going to try to talk to each other and that's the integration points. So here's a very simplistic scenario. If we take the string calculator (external link, opens in new tab) that we've all just written and now we'll have a requirement that let's assume there's a class called slow logger.
[03:15] (external link, opens in new tab) Now I'm calling it slow logger so that you remember not to use it in your own tests. But the requirement is that whenever you call add and you give it some numbers the sum that's returned is also sent to the logger. So we have some kind of auditing on the system. So again the requirement is that there is an arrow between string calculator and slow logger and every time you call add slow logger gets called. Now again slow logger here is a third party system.
[03:46] (external link, opens in new tab) We don't have control over it. If we could make it run in memory and make it run fast, maybe we do have control over it. But the fact is this is a slow logger because it uses threads and five different web services inside. Takes 5 seconds to run. We can't use it in our actual unit test. So the be next best thing is to try to create something that looks like the slow logger. And in Java, one of the best ways to make something look like another thing is to create an interface.
[04:18] (external link, opens in new tab) Makes them look the same. So we create we in we introduce a new interface into the system. We'll call it logger. And then we make slow logger implement that interface. But we need one more step. We need something that we can control. So we create a new class. I'm not using any kind of isolation frameworks (external link, opens in new tab) just yet. What I'm going to do is I'm going to create this handwritten. And this is an important skill that you need to know not to use an isolation or a mocking framework, but to do things without them.
[04:51] (external link, opens in new tab) Once you do, you can understand how these other frameworks actually work. You can choose to use them or not. So, we're going to create a new class called fake (external link, opens in new tab) logger in our test project. And fake logger is going to implement the logger interface (external link, opens in new tab) just like slow logger. But the difference is that fake logger is our class. So we can make it do whatever we want. For example, we can add a public field to it. We can we can we can override the write method and of course not do anything so that it will run fast.
[05:27] (external link, opens in new tab) So it looks like this. In my tests, the quickest way is to actually create my own little class called fake logger. Now, why am I not calling it mock logger? Any ideas? Because it might not be a mock object. So, let's talk about the the naming for a second. I call it fake logger because it looks like something else,
[05:58] (external link, opens in new tab) But it might not always be a mock object. What's the difference between a fake and a mock? A fake is something that looks like something else. A mock is a fake that I assert against. Okay? So if I assert against it, it's a mock object. Okay? Otherwise, it's a stub (external link, opens in new tab). But either way, they're both fakes. Okay? So a fake can either be a mock or a stub based on how you use it.
[06:31] (external link, opens in new tab) Now, why is it important to actually distin distinct to have a distinction between them? because you should only have a single mock per test at the worst case because you only want to check one thing per test. You can have multiple stubs but only a single mock object because a mock object is an end result (external link, opens in new tab) of the unit of work. We want to check one end result per test. If we check multiple end results and one of them fails, the others will never execute. So we won't know what's going on. So a mock object is really an assert, a hidden assert against a single end result.
[07:08] (external link, opens in new tab) Anyway, I take my fake logger and I override the write method and I implement logger. And notice that I just create a public field and I set it from the variable. I make it the simplest code (external link, opens in new tab) that I could possibly write because this doesn't exist anywhere but in the context of my unit tests. And then I'm going to write a new test right here. This is how my test would look. Notice first of all the naming add that's the entry point of the unit of work when called because I don't have a specific scenario it or I can say either always or when called.
[07:48] (external link, opens in new tab) So add always calls logger or add when called calls logger. And the word calls logger the word calls is important because I'm not actually doing logging. I'm calling a third party. So the naming is important and by looking at the name I can tell that there's probably a mock object in there. The second thing that I do that I create an instance of the fake logger in my unit test and then I send that fake logger somehow into the string calculator.
[08:21] (external link, opens in new tab) Now the string calculator has to have an a reference to the to the logger and one of the best ways to do it is by doing constructor injection (external link, opens in new tab) by by adding an argument to the constructor of string calculator. Now this also makes sure that this is nonoptional. So whoever is using string calculator has to send in a logger. If I wanted to make it optional I could make it a setter public setter or something. But I like to make things explicit so that I know exactly what my dependencies are.
[08:54] (external link, opens in new tab) And this also helps later if I'm going to use things like Spring to to create to do dependency injection (external link, opens in new tab) and inversion of control. Either way, I just added an argument to the string calculator and I'm going to send in the mock logger. And notice that I used a fake but I named the variable mock because in this unit test the fake logger is being used as a mock object.
[09:25] (external link, opens in new tab) And then I call add and I assert against the mock object. And this is important. I can look at the I can when I review this test I can say okay so you're asserting on a mock object. If I happen to call it mock stub or sorry lo log stub or stub log, it wouldn't make sense to say assert on a stub because a stub is something that just makes happy noises when you so that you can test something else. I only want to assert against mock objects.
[09:56] (external link, opens in new tab) So I just save things into the string of mock logger here. I save things into written and then I assert that written actually has something. And this is what the string calculator looks like. That's the only difference that I've made. And notice that the difference is that it takes a logger. The logger is actually part of production code. The fake logger is part of test code. So the logger interface now abstracts away the knowledge from string calculator. So string calculator doesn't need to know what is the type of logger that it