views:

134

answers:

3

Hi all,

I have two very similar specs for two very similar controller actions: VoteUp(int id) and VoteDown(int id). These methods allow a user to vote a post up or down; kinda like the vote up/down functionality for StackOverflow questions. The specs are:

VoteDown:

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
    Establish context = () =>
    {
        post = PostFakes.VanillaPost();
        post.Votes = 10;

        session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
        session.Setup(s => s.CommitChanges());
    };

    Because of = () => result = controller.VoteDown(1);

    It should_decrement_the_votes_of_the_post_by_1 = () => suggestion.Votes.ShouldEqual(9);
    It should_not_let_the_user_vote_more_than_once;
}

VoteUp:

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
    Establish context = () =>
    {
        post = PostFakes.VanillaPost();
        post.Votes = 0;

        session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
        session.Setup(s => s.CommitChanges());
    };

    Because of = () => result = controller.VoteUp(1);

    It should_increment_the_votes_of_the_post_by_1 = () => suggestion.Votes.ShouldEqual(1);
    It should_not_let_the_user_vote_more_than_once;
}

So I have two questions:

  1. How should I go about DRY-ing these two specs? Is it even advisable or should I actually have one spec per controller action? I know I Normally should, but this feels like repeating myself a lot.

  2. Is there any way to implement the second It within the same spec? Note that the It should_not_let_the_user_vote_more_than_once; requires me the spec to call controller.VoteDown(1) twice. I know the easiest would be to create a separate spec for it too, but it'd be copying and pasting the same code yet again...

I'm still getting the hang of BDD (and MSpec) and many times it is not clear which way I should go, or what the best practices or guidelines for BDD are. Any help would be appreciated.

A: 

You could probably factor out much of the repetition by just factoring out the setup of the tests. There is no real reason why the upvote spec should go from 0 to 1 vote rather than 10 to 11, so you can very well have one single setup routine. That alone will leave both test at 3 lines of code (or 4, if you need to call the setup method manually...).

Suddenly, your tests consist only of executing the action, and verifying the results. And whether it feels repetitive or not, I would strongly advise that you test one thing per test, simply because you want to know exactly why a test fails when you refactor something in a month and run all the tests in the solution.

UPDATE (see comments for details)

private WhateverTheTypeNeedsToBe vote_count_context = () => 
{
    post = PostFakes.VanillaPost();
    post.Votes = 10;

    session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
    session.Setup(s => s.CommitChanges());
};

And in your specification:

Establish context = vote_count_context;
...

Could this work?

Tomas Lycken
Good point. The thing is, these specs inherit from `SomeControllerContext`, where the main setup happens in (in a large `Establish context = () => {}`) and the `session.Setup(...)` happening in both specs cannot be placed in the main Establish because it would interfere with other specs inheriting from SomeControllerContext. I guess what you suggest would work if I did something like: `public class VoteSetup : SomeControllerContext { Establish ...}` and then `public class When_user_clicks_the_vote_down_button_on_a_post : VoteSetup { //spec }`. Will all the Establish in the chain be executed?
Sergi Papaseit
@Spapaseit, I'm no MSpec guru (I've never actually used it, but I'm getting more and more interested...) but it seems to me that you should be able to define a private field (static if need be) with the value of the lambda expression. I'll edit a code example into my post...
Tomas Lycken
+1  A: 

@Tomas Lycken,

I'm no MSpec guru either, but my (as of yet limited) practical experience with it leads me more towards something more like this:

public abstract class SomeControllerContext
{
    protected static SomeController controller;
    protected static User user;
    protected static ActionResult result;
    protected static Mock<ISession> session;
    protected static Post post;

    Establish context = () =>
    {
        session = new Mock<ISession>();
            // some more code
    }
}

/* many other specs based on SomeControllerContext here */

[Subject(typeof(SomeController))]
public abstract class VoteSetup : SomeControllerContext
{
    Establish context = () =>
    {
        post= PostFakes.VanillaPost();

        session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
        session.Setup(s => s.CommitChanges());
    };
}

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_up_button_on_a_post : VoteSetup
{
    Because of = () => result = controller.VoteUp(1);

    It should_increment_the_votes_of_the_post_by_1 = () => post.Votes.ShouldEqual(11);
    It should_not_let_the_user_vote_more_than_once;
}

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : VoteSetup
{
    Because of = () => result = controller.VoteDown(1);

    It should_decrement_the_votes_of_the_post_by_1 = () => post.Votes.ShouldEqual(9);
    It should_not_let_the_user_vote_more_than_once;
}

Which is basically what I already had but adding changes based on your answer (I didn't have the VoteSetup class.)

If my reputation were >=15 I'd vote your answer up, for I feel it has lead me in the right direction. I'm still hoping for some more answers to gather other points of view on the subject... :)

Sergi Papaseit
+6  A: 

I'll start with your second question: There is a feature in MSpec that would help with the duplication of the It fields, but in this scenario I would advise against using it. The feature is called Behaviors and goes something like this:

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_up_button_on_a_post : SomeControllerContext
{
    // Establish and Because cut for brevity.

    It should_increment_the_votes_of_the_post_by_1 =
        () => suggestion.Votes.ShouldEqual(1);

    Behaves_like<SingleVotingBehavior> a_single_vote;
}

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
    // Establish and Because cut for brevity.

    It should_decrement_the_votes_of_the_post_by_1 = 
        () => suggestion.Votes.ShouldEqual(9);

    Behaves_like<SingleVotingBehavior> a_single_vote;
}

[Behaviors]
public class SingleVotingBehavior
{
    It should_not_let_the_user_vote_more_than_once =
        () => true.ShouldBeTrue();
}

Any fields you want to assert on in the behavior class need to be protected static in both the behavior and the context class. The MSpec source code contains another example.

I advise against using behaviors because your example actually contains four contexts. When I think about what you're trying to express with the code in terms of "business meaning", four different cases emerge:

  • User votes up for the first time
  • User votes down for the first time
  • User votes up for the second time
  • User votes down for the second time

For each of the four different scenarios I would create a separate context that closely describes how the system should behave. Four context classes are a lot of duplicate code, which brings us to your first question.

In the "template" below there is one base class with methods that have descriptive names of what will happen when you call them. So instead of relying on the fact that MSpec will call "inherited" Because fields automatically, you put information on what's important to the context right in the Establish. From my experience this will help you a lot later when you read a spec in case it is failing. Instead of navigating a class hierarchy you immediately get a feeling for the setup that takes place.

On a related note, the second advantage is that you only need one base class, no matter how many different contexts with specific setup you derive.

public abstract class VotingSpecs
{
    protected static Post CreatePostWithNumberOfVotes(int votes)
    {
        var post = PostFakes.VanillaPost();
        post.Votes = votes;
        return post;
    }

    protected static Controller CreateVotingController()
    {
        // ...
    }

    protected static void TheCurrentUserVotedUpFor(Post post)
    {
        // ...
    }
}

[Subject(typeof(SomeController), "upvoting")]
public class When_a_user_clicks_the_vote_up_button_on_a_post : VotingSpecs
{
    static Post Post;
    static Controller Controller;
    static Result Result ;

    Establish context = () =>
    {
        Post = CreatePostWithNumberOfVotes(0);

        Controller = CreateVotingController();
    };

    Because of = () => { Result = Controller.VoteUp(1); };

    It should_increment_the_votes_of_the_post_by_1 =
        () => Post.Votes.ShouldEqual(1);
}


[Subject(typeof(SomeController), "upvoting")]
public class When_a_user_repeatedly_clicks_the_vote_up_button_on_a_post : VotingSpecs
{
    static Post Post;
    static Controller Controller;
    static Result Result ;

    Establish context = () =>
    {
        Post = CreatePostWithNumberOfVotes(1);
        TheCurrentUserVotedUpFor(Post);

        Controller = CreateVotingController();
    };

    Because of = () => { Result = Controller.VoteUp(1); };

    It should_not_increment_the_votes_of_the_post_by_1 =
        () => Post.Votes.ShouldEqual(1);
}

// Repeat for VoteDown().
Alexander Groß
Thanks again. I knew about behaviours (from de MSpec source code example indeed), but it felt like I would have to shoehorn it into my scenario; it didn't feel a natural fit. Brilliant answer, thanks a million.
Sergi Papaseit