DRY-ing very similar specs for ASP.NET MVC controller action with MSpec (BDD guidelines)


DRY-ing very similar specs for ASP.NET MVC controller action with MSpec (BDD guidelines)



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.


Why does this render as a list of “System.Web.Mvc.SelectListItem”s?

1:

Can I use the <%: tag on an application that will be hosted on IIS6?
I'll start with your second question: There is a feature in MSpec this would guidance with the duplication of the It fields, although in this scenario I would advise against using it. How to remove the link if value is 0 using asp.net mvcThe feature is called Behaviors and goes any thing like this:. DavCInt User Agent and Options Verb Requests
[Subject(typeof(SomeController))] public class When_user_clicks_the_vote_up_although ton_on_a_post : SomeControllerContext {     // Establish and Because cut for brevity. Automatic User Authentication Framework for Controllers in ASP.NET MVC?     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_although ton_on_a_post : SomeControllerContext {     // Establish and Because cut for brevity. getting started with Fluent nHibernate and asp.net MVC     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. Use of IsAssignableFrom and “is” keyword in C#The MSpec source code contains ananother example.. what would be the best method to override the validation of data in an mvc application I advise against using behaviors for the reason this your case actually contains four contexts. When I think around what you're endeavor 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 this 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 this have descriptive names of what will appear when you call them. So instead of relying on the fact this 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 guidance you a lot later when you read a spec in case it is failing. Instead of navigating a class hierarchy you immediately receive a feeling for the setup this takes place.. On a related note, the second advantage is this 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_although ton_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_although ton_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(). 

2:

@Tomas Lycken,. I'm no MSpec guru either, although my (as of yet limited) practical experience with it leads me more towards any thing 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>();             // any  more code     } }  /* many another 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_although ton_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_although ton_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 although adding changes based on your answer (I didn't have the VoteSetup class.). Your answer has lead me in the right direction. I'm still hoping for any more answers to gather another points of view on the subject... :).

3:

You could probably factor out enough 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 must 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 this you test one thing per test, simply for the reason this you want to know exactly why a test fails when you refactor any thing 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?.


87 out of 100 based on 72 user ratings 347 reviews

^