Am I writing my first MSpec specifications correctly?

Sekhat picture Sekhat · Jul 26, 2009 · Viewed 11.3k times · Source

I'm writing my first MSpec specifications and I wanted some guidance. I left the specs in the "pending" state, but the context is filled out. Are there any improvements to be made?

For reference, this is the story and first scenario:

Story: "Blog admin logs in to the system"

As a blog writer
I want to be able to log in to my blog
So that I can write posts and administer my blog

Scenario: "Logs in from the login page"

Given the user enters in correct credentials for a user in the system
When the user clicks the "Login" button
Then log the user in and redirect to the admin panel with a message 
stating that he logged in correctly

And the MSpec code (some parts snipped), notice that I had to alias the MSpec It delegate due to a conflict with Moq.It:

using MoqIt = Moq.It;
using ThenIt = Machine.Specifications.It;

[Subject("User tries logging in")]
public class When_user_enters_valid_credentials : With_user_existing_in_membership
{
    protected static ActionResult result;

    Because of = () =>
    {
        result = loginController.Login(validUsername, validPassword);
    };

    ThenIt should_log_the_user_in;
    ThenIt should_redirect_the_user_to_the_admin_panel;
    ThenIt should_show_message_confirming_successful_login;
}

public abstract class With_user_existing_in_membership
{
    protected static Mock<ISiteMembership> membershipMock;
    protected static string validUsername;
    protected static string validPassword;
    protected static LoginController loginController;

    Establish context =()=>
    {
        membershipMock = new Mock<ISiteMembership>();
        validUsername = "ValidUsername";
        validPassword = "ValidPassword";
        //make sure it's treated as valid usernames and password
        membershipMock
            .Setup<bool>(m => m.Validate(
                MoqIt.Is<string>(s => s == validUsername), 
                MoqIt.Is<string>(s => s == validPassword)))
            .Returns(true);
        loginController = new LoginController(membershipMock.Object);
    };
}

Answer

Alexander Gro&#223; picture Alexander Groß · Aug 2, 2009

The context looks good. I like the way you solved the conflicting It with aliases. I would argue that the Moq alias can be improved. Consider something sentence-like. For example, Param.Is<T> or Value.Is<T>.

Some notes, with code snippets, then the whole spec rewritten at the bottom.

The Scenario is your Subject

The Subject can be the Scenario from the story. Plus, it gets rendered with your test run report (especially nice in the HTML report).

[Subject("Login Page")]

Don't waste time on "With" named base classes

MSpec's creator, Aaron Jensen, has reverted from using the "With" syntax altogether. Context class names do not show up for any reports, so avoid spending time inventing a meaningful name.

public abstract class MembershipContext

The Given is your spec class name

Name the concrete spec class after the Given in your story. Especially since the base class name isn't reported anywhere, you could be losing half your context in the report! You should also avoid putting the name of the system under test in context class names. This makes your contexts friendlier to refactoring the system under test.

public class When_an_existing_user_enters_valid_credentials

Base spec classes should contain only general initialization

And are often unnecessary. They lead to separation of the Arrange and Act phases. Use a base class for common field initialization, like setting up mocked dependencies. But, you should not mock behavior in a base class. And you should not put context-specific information in the base class. In your example, the username/password. This way, you can create a second context with invalid credentials.

Establish context = () =>
{
    membership = new Mock<ISiteMembership>();
    loginController = new LoginController(membership.Object);
};

Fields in the concrete spec class should be private

It reduces the "ceremony" of the language in your test. You should place them below all of the MSpec specific delegates, as those parts of the spec tell most of the story.

static ActionResult result;

The Spec Overhaul

The spec here is an excellent example of establishing a global context MembershipContext and inheriting it in a context specific to the spec (thus, the additional Establish).

[Subject("Login Page")]
public class When_an_existing_user_enters_valid_credentials : MembershipContext 
{
    Establish context = () =>
    {
        membership
            .Setup<bool>(m => m.Validate(
                Param.Is<string>(s => s == username), 
                Param.Is<string>(s => s == password)))
            .Returns(true);
    };

    Because of = () => result = loginController.Login(username, password);

    It should_log_the_user_in;
    It should_redirect_the_user_to_the_admin_panel;
    It should_show_message_confirming_successful_login;

    static ActionResult result;
    const string username = "username";
    const string password = "password";
}

public abstract class MembershipContext 
{
    Establish context = () =>
    {
        membership = new Mock<ISiteMembership>();
        loginController = new LoginController(membership.Object);
    };

    protected static Mock<ISiteMembership> membership;
    protected static LoginController loginController;
}