This is the first part in a series of blog posts that will demo FakeItEasy through TDD-ing a simple Login Service using C#.
This is a port of Brett Schucherts excelent demo of Mockito in Java. As this is a demo of FakeItEasy, I will omit the refactoring part of Brett’s original example, please don’t miss it though as it can be found here. I’ve tried to stay as close to the original example as possible so that they can be easily compared.
The source for this example series can be found in a Mercurial repository at Google code. Each test implementation and following code update is a separate commit so you can easily update your repository to look at the full code at any given state. Find the repository here.
Getting started
- Create a new class library project in Visual Studio (or your IDE of choice).
- Create a new class library for the tests.
- Add a reference to the class library project from the test project.
- Download FakeItEasy and add a reference to FakeItEasy.dll from your test project. (Version 1.0.0.2 is used when writing this example.)
- Download NUnit and add a reference to nunit.framework.dll from your test project. (Version 2.5.0.9122 is used when writing this example.)
Writing the tests
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 FakeItEasy 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:
- All types used or needed by the underling LoginService will be represented as Interfaces (Interfaces will start with an I).
- All types used or needed by the underling LoginService will be created via FakeItEasy.
- I'm going to use Loose mocks - that is, you can call anything you want and the underling object will not complain. This is the default behavior of FakeItEasy.
- I'm going to minimally verify the expected resulting interactions (one assertion per test).
- I’ll only refactor the unit tests, for more on the refactoring of the production code see Brett’s original: refactoring to the state pattern.
Test 1 – Basic Happy Path
User Story
When a user logs in successfully with a valid account id and password, the account’s state is set to logged in.
The test
namespace FakeItEasy.LoginService.Tests { using FakeItEasy.Examples.LoginService; using NUnit.Framework; [TestFixture] public class LoginServiceTests { [Test] public void Should_set_account_to_logged_in_when_password_matches() { // Arrange var account = A.Fake<IAccount>(); A.CallTo(() => account.PasswordMatches(A<string>.Ignored)).Returns(true); var accountRepository = A.Fake<IAccountRepository>(); A.CallTo(() => accountRepository.Find(A<string>.Ignored)).Returns(account); var service = new LoginService(accountRepository); // Act service.Login("username", "password"); // Assert A.CallTo(() => account.SetLoggedIn(true)).MustHaveHappened(); } } }
Test description
- This test first creates a fake for an IAccount. There's no actual account class, just the interface. This fake 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.
- Create a fake of an IAccountRepository. Associate the fake IAccount with the fake IAccountRepository. When asking for any account with any username return the account fake created at the start of this method.
- 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 IAccountService, it is not responsible for deciding to which instance it should send messages.
- Actually send a login message, looking for account with username "username" and a password of "password". Notice that if things are configured correctly, any username will match as will any password.
- Use FakeItEasy to assert that the SetLoggedIn-method of the account was called.
Things Created for Compilation
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:
IAccount
namespace FakeItEasy.Examples.LoginService { public interface IAccount { bool PasswordMatches(string password); void SetLoggedIn(bool isLoggedIn); } }
IAccountRepository
namespace FakeItEasy.Examples.LoginService { public interface IAccountRepository { IAccount Find(string username); } }
LoginService
namespace FakeItEasy.Examples.LoginService { using System; public class LoginService { public void Login(string username, string password) { } } }
Failing test
Creating the test and adding all of theses classes gets my first test to Red with the following error:
Assertion failed for the following call: 'FakeItEasy.Examples.LoginService.IAccount.SetLoggedIn(True)' Expected to find it once but no calls were made to the fake object.
Passing the test
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:
namespace FakeItEasy.Examples.LoginService { using System; public class LoginService { private IAccountRepository accountRepository; public LoginService(IAccountRepository accountRepository) { this.accountRepository = accountRepository; } public void Login(string username, string password) { var account = this.accountRepository.Find(username); account.SetLoggedIn(true); } } }
Conclusion
There we have it, our first test case implemented, in the next part we’ll create another test.
Hey Patrick,
ReplyDeleteThanks for this blog entry which is very useful if you don't already have an authentication framework in place.
However, I'd like to pick your brains on how you'd simulate a log in for an out of the box ASP.NET MVC 2 web application.
I want to pass in real credentials using FakeItEasy but don't know how to do that. There are Rhino Mocks and Moq examples but I would much prefer stick with FakeItEasy.
TIA,
David
Do you have any links to the Rhino and Moq examples, I'm sure I can help you if I see those examples. Ping me for further info, my e-mail can be found over at the google code page.
ReplyDeleteNice example of how to use Mocking framework. Not advanced use but clean design. FakeItEasy and JustMock free edition are in my view the best frameworks on the market to do mocking.
ReplyDeleteGreat example! helped me to understand
ReplyDeletewefwefafasdfas
ReplyDeletegreat !!
ReplyDelete