Port of schuchert.wikispaces.com
Mockito.LoginServiceExample
I’m assuming you can download Mockito and get it in your classpath. So I’ll start with tests that implement some of the requirements from here.
However, in a nutshell:
What follows is a series of tests to get enough production code written to suggest a better implementation. The first purpose of this tutorial is to demonstrate using Mockito for all types other than the underling LoginService. This is close to a classic mockist approach, though it varies in that I’m emphasizing testing interaction rather than state and deliberately trying to write stable tests that do not depend too much on the underling implementation. In support of this:
When a user logs in successfully with a valid account id and password, the account’s state is set to logged in. Here’s a way to test that:
Part 1 | This test first creates a test-double for an IAccount. There’s no actual account class, just the interface. This test-double is configured so that no matter what password is sent to it, it will always return true when asked if a provided password matches its password. |
Part 2 | Create a test-double for an IAccountRepository. Associate the test-double IAccount with the test-double IAccountRepository. When asking for any account with an id equal to any string, return the account test-double created at the start of this method. |
Part 3 | Create a LoginService, injecting the IAcccountRepsitory in the constructor. This is an example of Inversion of Control, rather than the LoginService knowing which IAccountRepository to talk to, it is told which one to talk to. So while the LoginService knows which messages to send to an IAccountRepository, it is not responsible for deciding towhich instance it should send messages. |
Part 4 | Actually send a login message, looking for account with id “brett” and a password of “password”. Notice that if things are configured correctly, any account id will match as will any password. |
Part 5 | Use the Mockito method verify (confirm) that the method setLoggedIn(true) was called exactly once. |
To get this test to compile (but not yet pass), I had to create a few interfaces and add some methods to them. I also had to create a LoginService class:
Creating the test and adding all of theses classes gets my first test to Red with the following error:
While the stack trace looks a little daunting, the error seems clear enough. As you’ll see, adding a little bit of code in the LoginService class will get the test passing.
The test as written requires that the production code (LoginService) sends a message to a particular IAccount object. The LoginService retrieves accounts via its IAccountRepository, which it received during construction. So all we need to do is remember that particular IAccountRepository object and use it:
After three consecutive failed login attempts to the account, the account shall be revoked. Here’s such a test expressing this business rule (we’ll remove duplication in the tests after getting to green):
As before, there are 5 parts to this test:
Part 1 | Create an IAccount test-double. Unlike the first test, this test double never matches any password. |
Part 2 | Create an IAccountRepository test-double and register the IAccount test-double with it for any account id. |
Part 3 | Create the LoginService as before, injecting the IAccountRepository test-double. |
Part 4 | Attempt to login three times, each time should fail. |
Part 5 | Finally, verify that the account was set to revoked after three times. |
Notice that this test does not check that setLogedIn is not called. It certainly could and that would make it in a sense more complete. On the other hand, it would also tie the test verification to the underlying implementation and also be testing something that might better be created as its own test (so that’s officially on the punch-list for later implementation).
This test requires a new method, setRevoked(boolean value) to be added to the IAccount interface.
When you’ve done that, the test fails with an exception similar to the previous test. Next, it’s time to make the test turn green.
Here’s one way to make this test pass (and keep the first test passing):
Sure it is a bit ugly and we can certainly improve on the structure. Before doing that, however, we’ll let the production code ripen a bit to get a better sense of its direction. Instead, let’s spend some time removing duplication in the unit test code. Rather than make you work through several refactoring steps, here’s the final version I came up with:
This simply extracts common setup to an init() method. However, this cleanup really shortens the individual tests considerably. It also makes their intent clearer.
The first two tests have made good progress, however to keep the number of assertions per test small (so far one) and to make individual tests less dependent on the underlying implementation, this next test forces a fix to the code and probably would have been a better second test than one you just created.
This test takes advantage of the recent test refactoring. Before ever getting into the test method, the init() method:
There’s not much left:
It would have been reasonable to use a strict mock - one that does not allow any method invocations not explicitly specified. However, in this example I’m shying away from strict mocks.
This test did not require any existing classes to have new methods added.
Once the test executes, you’ll notice a failure. It’s a bit different from the previous example, but still it is fairly clear what happened. A method that should not have been called was called:
The LoginService.login method needs a little updating:
Verify that your code compiles and your tests pass.
This is one of those requirements you ask “Really?!” This requirement comes from an actual project, so while it might sound bogus, it is an actual requirement from the real world.
This test is a little longer because it requires more setup. Rather than possibly messing up existing tests and adding more setup to the fixture, I decided to do it in this test. There are alternatives to writing this test’s setup:
Since my primary purpose of this tutorial is practice using Mockito, I’ll leave it as is until I notice additional duplication.
There are 4 parts to this test:
Part 1 | Set the password matching to false on the account. |
Part 2 | Create a second account, with a never-matching password and register it with the account repository. Notice that this uses a particular account name, “schuchert”. Mockito, notices more specificwhen clauses over more general ones, so adding this after saying “for any string” is OK. This is a convenient default behavior (or is that behaviour as they would spell it?-). |
Part 3 | Login two times to the first account (both failing), then log in to a second account, also failing. That’s three failures in a row, but to two different accounts, so no account should be revoked. |
Part 4 | Verify that the secondAccount is not revoked. |
This test compiles without any new methods. It does fail with the following exception:
As with previous exceptions, the message tells you what you need to know. The account was incorrectly revoked.
To get this new test to pass, I added a new attribute to the LoginService class: previousAccountId. Then I updated the login method to take advantage of it:
This allows all tests to pass. Would it have been possible to do less? Maybe, but this was the first thing that came to mind. The code is starting to be a bit unruly. We’re just about ready to clean up this code, but before we do there are a few more tests.
In the actual problem, counting concurrent logins was somewhat complex. For this example, we’ll keep it simple. If you are already logged in, you cannot log in a second time. That’s simple enough:
This test first sets the password to matching. However, it also sets a new method, isLoggedIn, to always return true. It then attempts to login. The validation part of this test is in the (expected = AccountLoginLimitReachedException.class) part of the annotation.
First, create the new exception:
Next, add a new method to the IAccount class, isLoggedIn.
When you make these changes, the test will fail and the message indicates it expected an exception.
To get that exception thrown, simply make one small addition to the login method:
This is a final test to make sure the code handles the case of an account not getting found. This is not too hard to write:
This test takes advantage of the fact that more specificwhen clauses take precedence over more general ones. This test configures the account repository test-double to return null for the account “schuchert”. It then attempts the login, which should throw an exception.
To get this test to compile, you’ll need to add a new exception class:
When you make this change, the test will fail with a null pointer exception. The fix is quick and at the top of the method:
This should make all tests pass.
The next test is similar to the previous test. A revoked account does not allow logins:
This test is a repeat of the previous test, checking for a different result from a different starting condition.
You’ll need to add another exception, AccountRevokedException (as an unchecked exception) and a new method, isRevoked, to IAccount.
The only update to get to green is adding a check - a guard clause - similar to the previous test:
There are many more tests you could add to this system:
So there’s a lot let to make this a complete system. Even so, the code in the LoginService.login method is unruly. There are two problems:
The first issue suggests spending some time on an Account class and then moving some of the responsibility from the LoginService class to that new Account class. For example, instead of setLoggedIn(true), change it to login() and then respond accordingly:
If you do this, then you’ll be able to simplify the LoginServiceTest class because some of the tests will no longer belong there and instead will exist on the AccountTest. Creating AccountTest and Account classes is left as an exercise to the reader.
The second issue suggests the GoF State pattern. And in fact, that’s the next section.
In the real system, there were more requirements and the stream of requirements were fed to me over months. The underlying login service I created looked something like this simple version, just bigger. On the real project, the code became very hard to manage because I was not practicing refacoring aggressively enough at the time. I realized that the underlying solution would be made better by applying the GoF State pattern. In the actual solution, the LoginService had several methods, with many of the methods’ responses dependent on either the state of the login service or the account.
I made the change and sure enough supporting new requirements wasmuch easier. The remainder of this tutorial involves refactoring the current solution to use the GoF State pattern.
Here’s where we’re going:
In the typical state pattern, all or part of an object’s behavior depends on what has happened to it in the past. In this case there are two different sets of state:
Given the requirements so far, the state pattern is overkill. However, in the real system, refactoring to the state pattern made the implementationmuch easier, and more reliable as it turns out. So we’ll migrate the current solution of the LoginState.login method to delegate some of the responsibility to a state object.
We have tests passing and green. Our goal is to slowly migrate the code. Along the way we’ll also clean up some other problems.
Here’s our starting point:
This refactoring is a simplified version of Replace Type Code with State/Strategy. What we’ll do is somewhat simpler because we do not have a type code. Rather, the underlying code is state-based and it is this observation, along with difficulty of managing the code, that suggests following the refactoring steps described in Martin Fowler’s Refactoring book to get to the state pattern. And that is what follows.
Remember that refactoring is an attempt to improve the structure of the code without affecting the code’s behavior. In our case, the behavior is defined by our existing unit tests. We’ll take many small steps that have two goals in mind:
With this in mind our first effort will be to create the basic scaffolding. To do that:
Notice that you are not first creating unit tests. We already have unit tests in place. We are restructuring the code and the existing unit tests should keep us honest.
Also notice that for this first step, you’ve done nothing that will cause code to not compile or tests to fail. Go ahead and make sure your tests still pass, but that should be done by reflex anyway.
To keep things compiling and tests running, you’ll often do the following general steps:
That is, refactoring is more about copy, update, remove rather than directly moving code. This may take a bit longer, but it is less risky, keeps code compiling more often and makes it less likely you’ll break tests and lost track of what’s next.
In the case of moving to the state pattern, typically the method on the state object takes in a so-called context object. In this example, the context object is the LoginService object. Also, before we make the move, consider the parameters: a string for the account id and a string for the password. If you pass the account string into the LoginServiceState, then the state will have to use the account repository to look up the service. That’s fine, but it requires injection of the account repository into each of the state instances. Rather than doing that, we will instead pass in an IAccount. We’ll allow the lookup to happen in the LoginService and the resulting IAccount will be passed into the state.
Here is a first cut at creating that new method in LoginServiceState:
If you create this method, you’ll notice the following problems:
So as it is, we cannot add this method to the LoginServiceState class until we make a few changes. Rather than add the method and make the changes, back out this change and instead fix the problem of the accountId not being available first.
In fact, there’s a set of refactorings we can do that will support this change:
Simply add this method to IAccount: String getId();
There are only two lines in the bottom of the login method that use accountId (other than the first time it is used to look up the account). Those need to change:
Run your tests and you’ll see that two fail.
A single line added to the init() method in the unit test will get this to pass:
Verify your tests all pass.
Finally, extract the bottom part of the method:
Verify your tests all pass.
Notice that the code is well prepared to handle logging in after the IAccount is found. Start by copying verifyLoginAttempt into LoginServceState, rename it to login and add the missing previousAccountId and failedAttempts fields into the state object:
Verify your code compiles and your tests pass.
You’ll take three steps to complete this:
Next, modify the LoginService to have an instance of LoginServiceState and initialize it to AwaitingFirstLoginAttempt:
Your code should still compile and your tests should still pass.
Update the login method to delegate to the state:
Your code should still compile and your tests should still pass.
Finally, remove the verifyLoginAttempt method and the failedAttempts and previousAccountId fields:
Next, you’ll push the LoginServiceState.login method to each of the subclasses and then begin to remove the code not specifically related to each of the states.
First, push the login method as is into each of the subclasses and make the method abstract in the base class. (Note in most Java IDE’s this is a single refactoring command.)
Here’s the resulting LoginServiceState:
And here’s one of the substates:
Verify your code compiles and your tests pass.
Now for each of the sub states we’ll:
Note, before we can do any of what is to come, we need a way to set the next state. There are two obvious options:
Either option will work, I prefer the second option for two reasons:
In general, when I use the state pattern, I pick the second option from my previous experience with the state pattern.
If you are using a modern IDE, this is a simple refactoring. If not:
Verify that your tests still pass.
First, remove the parts of the login method that do not apply to the first time a password does not match:
To get this to pass, you’ll need to make a few additions:
(Note: I’m cheating a bit here, I’m adding the previousAccountId as a field in this class. It will eventually be removed from the abstract base class as it does not apply to the AwaitingFirstLoginAttempt class. This is an example of avoiding violating the Liskov Substitution Principle.)
Verify your code compiles and the tests all pass.
Moving through the state model, we’ll fix the second state:
To get this to compile, you’ll need to add a constructor to AfterSecondFailedLoginAttempt:
Verify your code compiles and your tests pass.
Now it’s time to update the final state:
Make sure your code compiles and your tests pass.
Here are some remaining cleanup steps (after each change, make sure your tests still pass):
Notice that there’s a lot of duplication in each of the three derived classes. Now, you’ll introduce the Gof Template Method Pattern.
The template method pattern expresses an algorithm in a base class with extension points implemented in a derived class. The extension points are:
Some external client issues a command, say X() as in the diagram above. The method X() has a number of steps (three in this example). The first and third steps are implemented in the base class. There is one part of the algorithm, the second step, that varies. Rather than attempt to implement it, the base class defers to an abstract method. The derived classes implement that abstract method to complete the algorithm.
Consider the game Monopoly. There are three kinds of locations around the board which players may purchase. These three kinds of locations are:
There is a standard algorithm for what happens when a player lands on a location:
Landing is a standard set of steps except for rent calculation. In terms of the template method pattern, there could be an abstract base class, say Real Estate, that has a method, landOn. Most of the work of landOn is written in the Real Estate base class. However, if rent needs to be charged, the RealEstate’s landOn method can defer the details of rent calculation to an abstract method it defines.
In the following drawing (which attempts to follow the UML 2.0 specification), thec methodd in thet base classe is the extension point. The base class deals with the basic validation like matching passwords and revoked accounts. It only defers what happens if the password does not match to the derived classes: « need to recreat this diagram » Rather than walk you through this refactoring, I’m just going to give you each of the classes.
After these four changes, make sure your code compiles and the tests pass.
When I looked at the login method in the LoginServcieState I realized there was a missing test. I also did not like the violation of the Dependency Inversion Principle. So we’ll fix those two things.
There is a problem with the current implementation of LoginServiceState.login, but there are no tests to verify that the problem exists. Rather than tell you what the problem is, here is one final test:
And here’s a fix to make this pass. In the LoginServiceState.login method, add the following line:
After this line:
What the first test does and why this fix works is left to the reader as an exercise.
The abstract class LoginServiceState depends on the concrete LoginService class, which violates the Dependency Inversion Principle. This is probably OK given that the state pattern is really a way to take part of the implementation of a class and extract it to a hierarchy. The combination of LoginService plus the LoginServiceState hierarchy is really a single logical unit.
Even so, let’s take this to its logical (extreme) conclusion as a way to demonstrate taking something too far.
Extract a base class for LoginService:
Replace all uses of LoginService with LoginServiceContext in the LoginServiceState hierarchy. Note that when I used the extract superclass refactoring in Eclipse, this was done automatically.
Make sure your code compiles and your tests pass.
Congratulations. You started by writing tests using Mockito. Once you had a number of tests in place, you refactored your production code from a bunch of nested if statements (an embedded state machine) to use the GoF State pattern. Once you got that working and cleaned up, you removed further duplication by introducing the Gof Template Method Pattern.
If you want to update your resume, it’s time to add:
I hope you enjoyed your journey.