DRY - very similar characteristics for ASP.NET MVC controller action with MSpec (BDD recommendations)

I have two very similar specifications for two very similar controller actions: VoteUp (int id) and VoteDown (int id). These methods allow the user to vote for the message up or down; as a function of voting up / down for StackOverflow questions. Specifications:

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

I have two questions:

  • How should I use EXISTING these two specifications? Is this even advisable or should I have one spec for each controller action? I know that I usually have to, but it seems to me that I repeat a lot.

  • It ? , It should_not_let_the_user_vote_more_than_once; , controller.VoteDown(1) . , , ...

BDD ( MSpec), , , BDD. .

+5
3

: MSpec , It, . Behaviors :

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

, , protected static , . MSpec .

, . , " ", :

, , . - , .

"" , , , . , , MSpec "" Because , , Establish. , , . , .

, , , .

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().
+8

@ ,

MSpec, ( ) - :

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

, , ( VoteSetup.)

. , ...:)

+1

, , . , upvote 0 1 , 10 11, . 3 ( 4, ...).

. , , , , , - .

( . )

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

:

Establish context = vote_count_context;
...

?

0
source

All Articles