Lesson 6 of 15
Mocks, Stubs and Fakes
Learn the difference between Mocks (external link, opens in new tab), Stubs (external link, opens in new tab) and Fakes, and use a hand written approach to creating Mock Objects.
In this lesson
Roy Osherove adds a requirement that the string calculator (external link, opens in new tab) writes to a slow logger and, without a framework, replaces the logger in the spec with a handwritten fake (external link, opens in new tab) logger class that has the same interface and saves the values for the assert. He defines a fake (external link, opens in new tab) as anything that looks like something else, a mock object (external link, opens in new tab) as a fake that you assert against and a stub (external link, opens in new tab) as any other fake, and passes the fake in through constructor injection (external link, opens in new tab). When a new requirement calls a web service if the logger raises an error, the fake web service becomes the mock and the fake logger a stub, and he advises no more than a single mock object per test (external link, opens in new tab).
Transcript
[00:02] (external link, opens in new tab) Let's start by talking about mock objects (external link, opens in new tab) fakes (external link, opens in new tab) and all the differences between them. If you remember that when we talked about what is a unit testing (external link, opens in new tab), do you remember what it means? >> Yeah. Okay. >> Yeah. What are the three end results (external link, opens in new tab) of a unit of work (external link, opens in new tab)? You remember >> state change (external link, opens in new tab). >> Okay. Okay. State based change, state based
[00:32] (external link, opens in new tab) A noticeable state change. Okay. >> Return value and >> third party call (external link, opens in new tab). >> Third party call. Which one of them would we try to use mock objects? >> Exactly. What about the others? Would we use any kind of mock objects there? >> I don't know. I don't know. Exactly. So that's what we'll learn. >> we'll learn about when to use stubs and mocks (external link, opens in new tab).
[01:05] (external link, opens in new tab) But let's talk about the string calculator (external link, opens in new tab) that we've all learned to love. So we have a method called add numbers. And I want to add a feature to the method. I want the method to write a message to the logger. I have a slow logger class. And in that slow library class there is a call to a database or a web service. So it's definitely a big dependency for me kind of a problem.
[01:36] (external link, opens in new tab) If I if I if I wanted to I could probably write a test that calls add numbers and then checks the database or log file or something but that test will take a long time or it could be very complicated to achieve. But I do want to have this functionality and I do want to do it in a test-driven (external link, opens in new tab) way. So what do I do? Instead of having slow logger, I can replace it with something that looks like slow logger but that I have full control of. So in this case, slow logger might be built by a different team.
[02:07] (external link, opens in new tab) It might actually have a bug I don't have full control over that class or if I do it uses the file system which I do not have full control or it's going to be very slow. So I'm going to change to something that I could control. Once I have control of something, it is no longer called a dependency. It's just called a helper for us and we can use it in our test. So what I'm going to do is I'm not going to create anything
[02:38] (external link, opens in new tab) Using a framework yet. What we're going to do is we're going to create our own class called a fake logger. That's very easy to do in Ruby. You just create a class that has the same interface as slow logger. So we're talking about duck typing here. So if slow logger has a method write with that takes a string then fake logger will just have a method right takes a string. But the difference is that fake logger will reside in our spec folder. Okay. And hopefully even in the same file as our specs
[03:11] (external link, opens in new tab) And it won't actually do any logging. it will just save the value and then put it in some public attribute that we can then later assert on. Now it's important to have this technique because sometimes doing things the manual way can actually be simpler to read and maintain than using the framework. And it's also important to do this because it will help you understand how the other frameworks work or isolation frameworks (external link, opens in new tab) or mocking frameworks work.
[03:45] (external link, opens in new tab) So in this case what I'm actually creating is a mock object but notice that the word mock is nowhere near the the slide. So what is a mock object? It is a fake object that I assert against. Okay, it is something that looks like something else and I'm going to assert against it. So in my test, what I'm going to try and do is I'm going to create an instance of the fake logger. I'm going to create an instance of the string calculator.
[04:16] (external link, opens in new tab) I somehow have to get the string calculator to use the fake logger. Maybe I'll send it as a parameter to the initialize method. It's called a constructor injection (external link, opens in new tab). Maybe I can put it as a dependency, as an attribute. That's called property injection. I have to get them to play together and then I will call add numbers on the string calculator but I will assert against fake logger dot some public attribute that I have added to it.
[04:46] (external link, opens in new tab) So I have full control over everything in this test and so I can say that it's a unit test. If I was using the real slow logger, I would call it an integration test (external link, opens in new tab) because it would use either the network or the file system or database or something that I do not have control over. For example, threads. Threads are considered integration testing because you do not have control over them. They make the test take longer. You have to start killing them and manipulating them and forking and joining and all this weird stuff.
[05:16] (external link, opens in new tab) So, it complicates the test. It's also a type of dependency. So fake logger is going to be a mock object. So let's see how that works. Okay, here we can see an example of making the logger a dependency on the string calculator. So I gone ahead and made a special class just for demos called string calculator one depth because we're going to have two dependencies later.
[05:48] (external link, opens in new tab) So this is just one dependency and basically what we want to have is this. I've gone ahead and added a the logger variable instantiation from the initializer here. And what I want to do is to notify the logger the add numbers. And this is what notify logger does. So, I'd like to be able to test this. And to be able to do that, what I've gone ahead and done
[06:22] (external link, opens in new tab) Is to create a fake logger. I call it a fake slow logger. What did the slow logger look like? This is the slow logger. See, we have a lib slow logger. And imagine that here we would have very slow this. So use the sheet C# sometimes this logger is very slow.
[06:53] (external link, opens in new tab) Okay. So we can't use this or maybe it's being controlled by a different team. Either way in my specs I've created my own fake slow logger. Okay, so fake slow logger has an attribute called numbers and we'll disregard will throw for now and we implement the method write
[07:24] (external link, opens in new tab) And we just save the numbers. So, this is the most simplest way I can think of of creating a fake slow logger. And I'm not using a framework yet because I want everyone to learn how to use handwritten fakes (external link, opens in new tab). Now, notice I'm not calling it not calling it stub (external link, opens in new tab) slow logger. I'm calling it fake slow. And there's a good reason. I might want to reuse it. And a fake object is anything that looks like anything else.
[07:56] (external link, opens in new tab) Based on how it's used, it's either a mock or a stub. I'll explain that this time I'm going to be using it as a mock. But in the next example, I'm going to be using this exact class as a stub. So here I have the adding feature and in the context of a logger attached it calls the logger. So here we just instantiate logger with the fake slow logger new instantiate the string calculator one dependency
[08:28] (external link, opens in new tab) And call the add method on it. Now then all I have to do is to go to this special attribute I've created on the fake logger called numbers over here. Now because this is class is part of our test project we have full control over it. We can add whatever things we want to and obviously we don't implement the real write method.
[09:01] (external link, opens in new tab) We just override it and create our own thing that just does nothing and just save the values saves the values. This way if the value here is actually got one then we know that string calculator logger was actually called. So again to do that I had to add a constructor argument to the string calculator one dependency
[09:34] (external link, opens in new tab) Which is over here. So I added one logger the logger here. I'm saving it and this is called constructure constructor injection. This is very important technique. It makes the dependencies very very explicit and easy to document. So in this case logger is a mock object.
[10:04] (external link, opens in new tab) But let's look at a more complicated scenario now. What if we have a new requirement that says now you have two dependencies. One of them is a slow logger and another is a web service. And as a system administrator, I would like to know when slow logger fails. I want to be notified in special web service when that happens. So now what I would want to do is to somehow fake make slow logger raise an error in my test
[10:35] (external link, opens in new tab) And then if an error is caught in the string calculator from slow logger then web service should be called with the error details. So now I have this interesting dependencies one of them should raise an error the other one one of them should be called. So in this case, which one is the mock object? What would I assert against in this case?
[11:06] (external link, opens in new tab) I would assert against the web service. So what is a web service? No, it's a mock object. If I assert against it, I'll I'll create a fake logger, but I'll also create a fake web service. And then I'll assert against the fake web service, and that would make it a mock. So what about the fake law in this case? What would I assert against it? No, I don't need to assert against it. I have a test that proves it gets false. So I don't need to assert against it in a new test. But if I don't assert against it, it becomes a stub.
[11:39] (external link, opens in new tab) That's it. So one of them is a mock and one of them is a stub. Why is it important to differentiate between a mock object and a stub? Because as a rule of thumb, you should have no more than a single mock object per test (external link, opens in new tab) perspective. Because a mock object represents an end result, right? It's a call to a third party. If you're if you have multiple mock objects, you're testing in the same test in the same spec that you're calling multiple third parties.
[12:11] (external link, opens in new tab) Each one of them should be in a separate test because each test is a feature. Even worse, if you have multiple mock objects and one of the calls to one of them fails, all the calls, all the asserts below that line will never execute. So you won't have as much information. So separating into multiple specs is also a good idea because you'll get more information about everything that passed and everything that failed. Okay, so mock objects are usually one per test, no more.
[12:48] (external link, opens in new tab) Sorry. >> Yeah, it's based on the end result. So if I if I have a test for a value based test then I'm asserting that I got back a value I would not have a mock object in the same test because there are two different end results two different concerns should be in two different tests because if one of them fails the other one will never execute and also it makes the test name (external link, opens in new tab) less readable because testing two things at once. And if I have a statebased test, I wouldn't have a mock object because it's another form of end result.
[13:20] (external link, opens in new tab) So, never mix multiple end results testing in the same test. A mock object is a form of end result testing. End result testing. Okay. Okay. So, how do I now this time since I have the two dependencies, I need to do two things. First, I have to make the create a fake logger and somehow make simulate an exception. So what I've done is I've gone ahead and if we look at the fake logger here,
[13:51] (external link, opens in new tab) I've added a will throw accessor and a simple line called raise whatever is in will throw if will throw is not. This is important because then my test can do something simple like this. context given a throwing logger in a web service it calls the web service. So I'm creating the fake logger
[14:22] (external link, opens in new tab) And I'm saying logger will throw the fake error and now I'm going gone ahead and created a fake web service class that looks like this. And this is basically the same interface that the web service has. But again, instead of the write method, I just overwrite it and do whatever I want, which is just to save the value into an accessor accessor. Then I can use that accessor in my tests
[14:56] (external link, opens in new tab) And basically say web service numbers should equals fake error. And when I instantiate, I've refactored string calculator one dependencies to now have two dependencies. And those two dependencies are in the initializer. So I send in the logger and the web service and I call add. And then I check the web service. So again, I've added a new functionality to the fake logger class where I can tell it to throw different things.
[15:30] (external link, opens in new tab) I'm still saving the value, but notice that in this case, the logger is no longer a mock object because I'm not asserting anything against it. I'm only asserting against the web service. So a mock object is anything that's fake and you can you assert against it. So that means that the web service is the mock object and that makes the logger a stub. So everything that you don't assert against and it's fake, that's called a stub.
[16:00] (external link, opens in new tab) Now why is it important to differentiate between them again? It's because usually you want to have only a single mock object in your test because a mock object is testing one end result. So if you have multiple mock objects, you're basically testing multiple things in the same specification and if one of them fails, the line after that will never execute. So you won't know if the second one is actually working. That's why you want to separate for each single test or spec to have no more than a single mock object.
[16:34] (external link, opens in new tab) But you can have multiple steps. You can have multiple dependencies, but you're only checking that one of them got called at any specific time because each call to a third party dependency is literally its own feature. This is the exercise we're going to do in a second. But this is the terminology. So we have fakes. And if you verify against it, it's a mock otherwise it's a stub. Now there is a terminology in a book called xunit test patterns which is a really good reference.
[17:08] (external link, opens in new tab) But that book has a lot of terminology on different types of fake objects and each one of them does things just a little bit differently. And I find that this is actually very confusing for a lot of people and there is no reason to differentiate so much. So I would only differentiate between a stub and a mock object because you need to know that you only have one of those and you can have many stubs if you want to. So I just use a very very simple terminology that everything is a fake and based on how you use it, it's either a mock or a stub.
[17:40] (external link, opens in new tab) So a mock is an example of a fake. Stub is an example of fake. If you assert against it, that fake is also a mock object. Okay. Okay. So, what we're going to do now is we're going to go through this exercise. Okay. For you and for the viewers at home, I want you to go through this