Lesson 4 of 14
Introduction to Hand Written Fakes, Mocks and Stubs
Roy Introduces the idea of mocks (external link, opens in new tab), fakes (external link, opens in new tab) and stubs (external link, opens in new tab) and shows how to hand write them (external link, opens in new tab), and when it makes sense to hand write them.
In this lesson
With a login manager whose save method must also call a logger written by another team, Roy Osherove introduces an ILogger interface (external link, opens in new tab) so a hand-written fake (external link, opens in new tab) logger can replace the real one. A fake (external link, opens in new tab) the test asserts against is a mock object (external link, opens in new tab), one it does not assert against is a stub (external link, opens in new tab), and the same class can serve as either. He also explains that injecting the fake through a constructor, rather than a property, tells the user of the API the dependency is not optional.
Transcript
[00:25] (external link, opens in new tab) So now that we've done a simple replacement of an object, let's talk about the idea of mock objects (external link, opens in new tab). and stubs (external link, opens in new tab). let's talk about a more interesting case where where things are not built in where we actually have to build a UI a fake (external link, opens in new tab) object. So imagine that you have a class called login manager.
[00:56] (external link, opens in new tab) Login manager already uses a logger class and there is a method called save. Now the point of the save method is to to save an object. But if an object has a property that's something that is too big for example then what we should do is we should also log the fact that there was an object that was saved that was too large.
[01:28] (external link, opens in new tab) So the requirement is that there are actually two requirements save and also write to the logger if there is something that happens or call the logger. Now let's assume that the logger is written by a different team. So it's actually not under our control or maybe it's actually not even done yet. Maybe it was written to maybe the code was written but it was not it's not finished yet.
[01:58] (external link, opens in new tab) So every time we want to actually call the logger we get an exception. So now we actually have an interesting situation where our team is depending on a different team. the team that writes the logger and we actually have to wait for them to finish the job. Or maybe the logger takes such a long time that we don't want to use it in our real tests. So instead of using the real logger, we can replace it with something else.
[02:29] (external link, opens in new tab) We can introduce a new interface into the system. to be able to replace something in a static language like C#, it has to inherit from some from the same object or implement the same interface. So we can introduce an ILogger interface (external link, opens in new tab). So now instead of logger, we create a new interface and it only has one method which is write just like logger has. But now in the constructor for login manager, there's an ILogger instead of a logger. So it doesn't care which logger it uses.
[03:01] (external link, opens in new tab) And this ILogger is actually part of the production code. It's not part of the tests. But now because I have that interface in my tests, I can create a new class that I create by hand called fake logger. And the nice thing about fake logger is that it's actually part of the test project. So it implements the same interface, but because it's in the test project, it doesn't have to do anything. I just implement the methods and they're empty.
[03:33] (external link, opens in new tab) They're not really doing anything. In fact, I can also override the methods and save the values that were sent in. Okay, so I can save the values and put them in, let's say, a public field or something like that. And in my test, I can use a fake logger instead of a real one. send it to the constructor of the login manager. And now in my test, I'll create the login manager, send it to the the fake logger, call save, and then assert against the fake logger that some field contains a string or something like that.
[04:10] (external link, opens in new tab) Just like I did with the string builder in the previous exercise, this time there is no string builder. There is no built-in API. So I created my own API for this, something that I have full control over. So when we have a fake object and I specifically call this class a fake logger not a mock logger or sub or anything like that it's a fake logger because it can replace something but when I have a fake object that I also assert against
[04:41] (external link, opens in new tab) Then that's called a mock object. The concept of a mock object is that there's something fake, not real in the system. But I'm also but it also decides whether my test failed or not. So if it has the power to break my test and decide that that my test failed, then it's a mock object. But if it's a fake something and I'm not asserting against it, then it's just a stub. So there's the idea of a fake and it's either a stub or a mock based on how I use it.
[05:13] (external link, opens in new tab) And if I keep this class clean enough, I can reuse it to be a mock in one test and then the same class can be used as a stub in another test. That's a that's that's important because we don't want to have 15 different classes in our test projects for for a single type of object. So let's see that. This is a demo of a class called login manager. Now I called it login manager with mock but usually the word mock will not be part of production code just for demos.
[05:55] (external link, opens in new tab) Now I already went ahead and created the interface for ILogger here. So there's an interface for ILogger. It has just one method called write. There's also a logger exception but we don't care about that for for now. We have a constructor and here I made a design decision that I can send in the ILogger through a constructor. But I could also have chosen not to add a constructor parameter. I could have chosen to add a property or a method that takes in and sets up a private field.
[06:28] (external link, opens in new tab) So what's the difference? What when would I choose to use a constructor and when would I choose to use something else to inject that fake object? Any ideas? Why would I use a constructor? >> What's the difference between a constructor and a property? >> You have to put it there. >> Yeah, it's not optional. Good. I have to use the constructor. So when I put an argument as a constructor, I'm actually telling the user of my API, this is not optional.
[07:01] (external link, opens in new tab) You have to provide this. That's an important design decision because it means it can never be null. For example,