Sunday, June 20, 2010

FakeItEasy Login Service Example Series – Part 6

This is the sixth part in the series of posts where I’m porting Brett Schucherts excelent demo of Mockito in Java to C# and FakeItEasy.

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.

Part 1 can be found here.

Part 2 can be found here.

Part 3 can be found here.

Part 4 can be found here.

Part 5 can be found here.

Test 6 – Exception thrown when account is not found

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:

The test

[Test]
public void Should_throw_when_account_does_not_exist()
{
    // Arrange
    A.CallTo(() => this.accountRepository.Find(A<string>.Ignored)).ReturnsNull();

    // Act, Assert
    Assert.Throws<AccountNotFoundException>(() =>
        this.service.Login("username", "password"));
}

Test Description

This test takes advantage of the fact that later configurations of a fake object takes precedence over earlier configurations, this differs from the way that Rhino Mocks works where earlier configurations has precedence.

A login is attempted which should fail.

Things Created for Compilation

To get this test to compile, you'll need to add a new exception class:
AccountNotFoundException.

namespace FakeItEasy.Examples.LoginService
{
    using System;

    public class AccountNotFoundException
        : Exception
    {
    }
}

Code Updated to get Test to turn Green

The test currently fails with a null reference exception, a quick fix is needed at the top of the method.

namespace FakeItEasy.Examples.LoginService
{
    using System;

    public class LoginService
    {
        private IAccountRepository accountRepository;
        private int numberOfFailedAttempts;
        private string previousUsername;

        public LoginService(IAccountRepository accountRepository)
        {
            this.accountRepository = accountRepository;
            this.previousUsername = string.Empty;
        }

        public void Login(string username, string password)
        {
            var account = this.accountRepository.Find(username);

            if (account == null)
            {
                throw new AccountNotFoundException();
            }

            if (account.IsLoggedIn)
            {
                throw new AccountLoginLimitReachedException();
            }

            if (account.PasswordMatches(password))
            {
                account.SetLoggedIn(true);
            }
            else
            {
                if (this.previousUsername.Equals(username))
                {
                    this.numberOfFailedAttempts++;
                }
                else
                {
                    this.numberOfFailedAttempts = 1;
                    this.previousUsername = username;
                }
            }

            if (this.numberOfFailedAttempts == 3)
            {
                account.SetRevoked(true);
            }
        }
    }
}

Friday, June 4, 2010

FakeItEasy Login Service Example Series – Part 5

This is the fifth part in the series of posts where I’m porting Brett Schucherts excelent demo of Mockito in Java to C# and FakeItEasy.

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.

Part 1 can be found here.

Part 2 can be found here.

Part 3 can be found here.

Part 4 can be found here.

Test 5 – Do Not Allow a Second Login

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:

The Test

[Test]
public void Should_not_allow_concurrent_logins()
{
    // Arrange
    A.CallTo(() => this.account.PasswordMatches(A<string>.Ignored)).Returns(true);
    A.CallTo(() => this.account.IsLoggedIn).Returns(true);

    // Act, Assert
    Assert.Throws<AccountLoginLimitReachedException>(() =>
        this.service.Login("username", "password"));
}

Test Description

This test first sets the password to matching. However, it also sets a new property, IsLoggedIn, to always return true. It then attempts to login. The validation part of this test is in the Assert.Throws-part, which is a feature of NUnit.

Things Created for Compilation

First, create the new exception:
namespace FakeItEasy.Examples.LoginService
{
    using System;

    public class AccountLoginLimitReachedException : Exception { }
}

Next, add a new property to the IAccount interface, “IsLoggedIn”.
When you make these changes, the test will fail and the message indicates it expected an exception.

Code Updated to get Test to turn Green

To get that exception thrown, simply make one small addition to the login method:

namespace FakeItEasy.Examples.LoginService
{
    using System;

    public class LoginService
    {
        private IAccountRepository accountRepository;
        private int numberOfFailedAttempts;
        private string previousUsername;

        public LoginService(IAccountRepository accountRepository)
        {
            this.accountRepository = accountRepository;
            this.previousUsername = string.Empty;
        }

        public void Login(string username, string password)
        {
            var account = this.accountRepository.Find(username);

            if (account.IsLoggedIn)
            {
                throw new AccountLoginLimitReachedException();
            }

            if (account.PasswordMatches(password))
            {
                account.SetLoggedIn(true);
            }
            else
            {
                if (this.previousUsername.Equals(username))
                {
                    this.numberOfFailedAttempts++;
                }
                else
                {
                    this.numberOfFailedAttempts = 1;
                    this.previousUsername = username;
                }
            }

            if (this.numberOfFailedAttempts == 3)
            {
                account.SetRevoked(true);
            }
        }
    }
}
 

Note that this code would’ve been refactored a number of times if it was not example code. -Patrik