Monday, February 14, 2011

FakeItEasy breaking change for assertions

In the latest version of FakeItEasy (version 1.6.4062.205) there is a breaking change in how repeats are specified in assertions. The reason for this is that the old way – while logically correct – was not intuitive to a lot of people.

Before you could write “Repeated.Once” and that assertion would pass even though the method you were asserting on had been called twice. Now, logically this makes perfect sense, a call that has happened twice has happened once. The problem is that I’ve noticed that people doesn’t think that way and therefore assertions has been wrong.

The reason I hasn’t changed this before is that if I did change it it would be a breaking change and it would just change under the feet of people that had figured the behavior out and I was reluctant to make a breaking change that doesn’t break the build.

The solution I came up with is one that does break the build. You can no longer write “Repeated.Once”, you have to write “Repeated.Exactly.Once” or “Repeated.AtLeast.Once” or “Repeated.NoMoreThan.Once” or any other repeat twice, Times(4) and so forth.

Hopefully you agree with my decision that it was important to break the build in this case and I hope that it doesn’t cause too much frustration, it is actually a quite straight forward matter of find and replace.

Saturday, February 5, 2011

Experimental branches in Mercurial

Branching in Mercurial is easy, it’s so easy that most of the times you’re branching you don’t even know that you’re doing it. Have you ever had to merge? I guess you have, well guess what, when you’re merging, you’re merging two branches! Ever got an error when pushing stating that a push would create two heads in the remote repository? Head == branch.

I had a conversation the other day with a fellow developer about Git, I asked him what the main benefits of Git was. The thing is I’m pretty much Git illiterate. One of the things he said was the ease of branching and how local “throw away” branches can’t be done in Mercurial. First of all, I don’t know exactly how easy this is in Git so it might be harder in Mercurial but it’s definitely not hard. Let me show you!

I’ll use TortoiseHG in this example since it’s visual and makes for better examples but it is just as easy to do it from the command line.

Let’s start with a repository that contains a code file:

image

Now, this class is boring, it only writes hello to the console and I’d like to spice it up a little, so I’ll do a spike. Let’s make it say “Hello Europe!”. Said and done, I edit the file and commit, now my repository looks like this:

image

Now the thing is, while “Hello Europe!” is good I’d like to explore something differnt, let’s make it say “Hello World!” that’s so much more international. Let’s go back to before the change:

image

Now, I change the code again, this time to “Hello World!” then I commit, now the repo looks like this:

image

See, two branches, I could have backed to any point back in the revision history to branch from there. Now I’ve decided to go with the “Hello World!”-change and I want to push it to a central repo, I don’t want to include the “Hello Europe!” spike.

Go into the repository explorer and click the “Determine and mark outgoing changesets”-button. This will mark change sets not already in the repository you’re pushing to with arrows:

image

Right click the head of the branch you want to push and select “Push to here”. Now the repository you were pushing to will contain the following changesets:

image

That’s pretty much all there is to it! One more thing though, what about deleting local branches that you don’t want to keep? This can be done with the strip command in the MqExtensions for Mercurial.

Happy branching!

Monday, October 4, 2010

Fixture initialization in FakeItEasy

I added a new feature to FakeItEasy that makes it – you guessed it – easy to initialize test fixtures with fakes.

Instead of calling A.Fake<WhateverTypeYourFakingHere>() once for every fake you have in your test fixture you can simply tag the variables or properties you want to be fakes with the “FakeAttribute” and then call the method “Fake.InitializeFixture(this)” in your fixture setup:

[TestFixture]
public class InitializeFixtureIntegrationTests
{
    [Fake] IFormatProvider formatProvider;
    [Fake] IFormattable formattable;

    [SetUp]
    public void SetUp()
    {
        Fake.InitializeFixture(this);
    }

        
    [Test]
    public void Should...

Sunday, September 26, 2010

Extending exception messages in FakeItEasy

One of the main features of FakeItEasy is the informative exception messages, let’s say we have the following types:

public interface IPersonRepository
{
    void Save(Person personToSave);
}

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime DateOfBirth { get; set; }
}

Now, let’s create a test that shows us what the exception messages in fake it easy looks like when asserting:

public void Extending_exception_messages()
{
    var repository = A.Fake<IPersonRepository>();

    repository.Save(new Person { FirstName = "Patrik", LastName = "Hägne", DateOfBirth = new DateTime(1977, 4, 5) });

    A.CallTo(() => repository.Save(A<Person>.Ignored)).MustNotHaveHappened();
}

This will yield an exception message that looks like this:

  Assertion failed for the following call:
    'FakeItEasy.Examples.IPersonRepository.Save()'
  Expected to find it exactly never but found it #1 times among the calls:
    1.  'FakeItEasy.Examples.IPersonRepository.Save(
            personToSave: FakeItEasy.Examples.Person)'

While this is a good exception message it doesn't say a whole lot about the person object that was used in the call to save. You could ofcourse in this particular instance override the ToString-method of person to fix this, but a lot of times you don’t have control over the types you use (for example the framework types). Now, fortunately, in the latest release of FakeItEasy, there is a dead simple way for you to provide your own formatter for printed argument values.

All you have to do is in your testproject define a class that inherits the FakeItEasy.ArgumentValueFormatter<T>-class where T is the type of class you wish to provide custom formatting for.

Here’s an example of such a formatter for the Person-type:

public class PersonArgumentFormatter
    : ArgumentValueFormatter<Person>
{
    protected override string GetStringValue(Person argumentValue)
    {
        return string.Format("Person named {0} {1}, date of birth {2:yyyy-MM-dd} ({3} days old).",
            argumentValue.FirstName,
            argumentValue.LastName,
            argumentValue.DateOfBirth,
            DateTime.Now.Subtract(argumentValue.DateOfBirth).TotalDays);
    }
}

Now, let’s run the same test again and see what the exception message looks like: (I’ve split the message over several lines in this post so that the message will not be truncated by the blog).

  Assertion failed for the following call:
    'FakeItEasy.Examples.IPersonRepository.Save()'
  Expected to find it exactly never but found it #1 times among the calls:
    1.  'FakeItEasy.Examples.IPersonRepository.Save(
            personToSave: Person named Patrik Hägne,
                          date of birth 1977-04-05 (12227,874689919 days old).)'

Saturday, July 24, 2010

FakeItEasy Login Service Example Series – Part 7

This is the seventh and last 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.

Part 6 can be found here.

Test 7 – Should not be able to log in to revoked account

The next test is similar to the previous test. A revoked account does not allow logins:

The test

[Test]
public void Should_throw_when_account_has_been_revoked()
{
    // Arrange
    A.CallTo(() => this.account.IsRevoked).Returns(true);

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

 

Test Description

This test is a repeat of the previous test, checking for a different result from a different starting condition.

Things Created for Compilation

You'll need to add another exception, AccountRevokedException  and a new property, IsRevoked, to IAccount.

Code Updated to get Test to turn Green

The only update to get to green is adding a check - a guard clause - similar to the previous test:

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

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

    if (account.IsRevoked)
    {
        throw new AccountRevokedException();
    }

    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);
    }
}

Summary

This has been a port of Brett’s original tutorial, his tutorial does not end here though no more tests are added. Please continue reading his tutorial.

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