BDD with Specflow, Moq and Microsoft Unit Testing framework

BDD (Behavior Driven Development) is very popular in these days. I know a lot of companies used BDD in our projects. So I also had some curios about it and I applied for my current project. I used Specflow, Moq and Microsoft Testing framework for working. I did it and found out some things very interested in. In this post I will not focus on what it BDD? What is Mock, stub or how to write a unit testing? Readers can easy find on internet, just googling it and everything will be okay. I just focus on the some best things I found when I implemented the BDD on my code. I used AAA (Arrange-Act-Assert) and GWT (Given-When-Then) patterns in my code. They are clearly and easily to understand and become standard when you write unit testing and mocking.

Now we discuss about a problem when we write unit testing and how to get rid of it. So what's problem? When we write unit testing and try to arrange a lot of things to testing. And the code will be messy and a line of code will become unmanageable, so it is hard to maintain at will. I will give you a simple sample. Assuming we have a small application for get all articles from database. To keep it simple I will not use ORM for communicate and mapping object between Object Model and Database. So the article will have a lot of information to populating. Some of them are Category, Article Information, Author Information, Article content, Article Media Item, Keyword, Topic and Sub topics. And we need to populate all of this, look like a complicate scenario.

We try to make it work. And it has many repositories inside the article manager class. So that mean your mock objects will equal with number of repositories in article manager class. What's happen with your test class? You will declare an object for mock and in its constructor; you will mock it and set it to parameter constructor for article manager class. Like this

var dummyRepositoryMock1 = new Mock<IDummyRepository1>();

var dummyRepositoryMock2 = new Mock<IDummyRepository2>();

var dummyRepositoryMock3 = new Mock<IDummyRepository3>();

var dummyRepositoryMock4 = new Mock<IDummyRepository4>();

var dummyRepositoryMock5 = new Mock<IDummyRepository5>();

var dummyRepositoryMock6 = new Mock<IDummyRepository6>();

// prepare the stub objects for return

var retObject1 = ...;

var retObject2 = ...;

var retObject3 = ...;

var retObject4 = ...;

var retObject5 = ...;

var retObject6 = ...;

dummyRepository1.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects1);

dummyRepository2.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects2);

dummyRepository3.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects3);

dummyRepository4.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects4);

dummyRepository5.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects5);

dummyRepository6.Setup(framework=>framework.SomeMethodCall(It.IsAny<int>())).Returns(retObjects6);

and the constructor of Article manager class will be

var articleManager = new ArticleManager(

dummyRepositoryMock1.Object,

dummyRepositoryMock2.Object,

dummyRepositoryMock3.Object,

dummyRepositoryMock4.Object,

dummyRepositoryMock5.Object,

dummyRepositoryMock6.Object);

So how can we optimize a line of code for this processing? I write the abstract class like this

    public abstract class AutoMockObject
    {
        private readonly IDictionary<Typedynamic> _mocks;

        protected AutoMockObject()
        {
            _mocks = new Dictionary<Typedynamic>();
        }

        public virtual void Register<TObject>(Action<Mock<TObject>> action) where TObject : class
        {
            var mockObject = new Mock<TObject>();
            _mocks.Add(typeof(TObject), mockObject);
            action(mockObject);
        }

        public virtual TObject Resolve<TObject>() where TObject : class
        {
            dynamic mock;

            return !_mocks.TryGetValue(typeof(TObject), out mock) ? null : mock.Object;
        }
    }

Make sure you test class should be inherited from AutoMockObject. And finally I just mock it with a bit effort as below

            Register<IArticleRepository>(mock =>
                                             {
                                                 mock.Setup(framework => framework.GetAll()).Returns(GetAllArticleStub.ArticleList);
                                                 mock.Setup(framework => framework.GetKeywordsByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.KeywordList);
                                                 mock.Setup(framework => framework.GetTopicsByArticleId(It.IsAny<int>(), false)).Returns(GetAllArticleStub.TopicList);
                                                 mock.Setup(framework => framework.GetTopicsByArticleId(It.IsAny<int>(), true)).Returns(GetAllArticleStub.TopicList);
                                             });

            Register<IArticleCategoryRepository>(mock => mock.Setup(framework => framework.GetById(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleCategory));
            Register<IArticleInfoRepository>(mock => mock.Setup(framework => framework.GetArticleInformationById(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleInformation));
            Register<IAuthorInfoRepository>(mock => mock.Setup(framework => framework.GetAuthorInformationById(It.IsAny<int>())).Returns(GetAllArticleStub.AuthorInformation));
            Register<IArticleContentRepository>(mock => mock.Setup(framework => framework.GetSummaryByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleContentList));
            Register<IArticleMediaItemRepository>(mock =>
                                                      {
                                                          mock.Setup(framework => framework.GetArticleMediaItemByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleMediaItemList);
                                                          mock.Setup(framework => framework.GetArticleMediaItemAltTextByArticleMediaItemId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleMediaItemAltTextList);
                                                      });

            _articleManager = new ArticleManager(
                    Resolve<IArticleRepository>(),
                    Resolve<IArticleCategoryRepository>(),
                    Resolve<IArticleContentRepository>(),
                    Resolve<IReferenceRepository>(),
                    Resolve<IArticleInfoRepository>(),
                    Resolve<IAuthorInfoRepository>(),
                    Resolve<IPublishingArticleRepository>(),
                    Resolve<IArticleMediaItemRepository>()
                );

Look like simple and elegant, isn't it? Just a little code for get rid of messy and redundant code.

Now I will show you how can I make all solutions working.

The first thing I will write a feature like this

Feature: Get all articles from Repository
In order to get all articles
As a cms user
So that I can get and process in all articles

@GetAllArticles
Scenario: Get All articles
Given I don't input any parametters
When I call the GetAllArticle function from service
Then the result should be a list of article

@GetAllArticlesWithError
Scenario: Get All articles with error
Given I dont input any parametters
When I call the GetAllArticle function from service
Then the result should be a exception

Add App.config and add some line of code as below:

  <configSections>
    <section
       name="specFlow"
       type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow"/>
  </configSections>
  <specFlow>
    <unitTestProvider name="MsTest" />
  </specFlow> 
And after that Specflow will generate the code for Microsoft Testing framework (default it will generate for NUnit testing) as below
// ------------------------------------------------------------------------------
//  <auto-generated>
//      This code was generated by SpecFlow (http://www.specflow.org/).
//      SpecFlow Version:1.7.0.0
//      SpecFlow Generator Version:1.7.0.0
//      Runtime Version:4.0.30319.431
// 
//      Changes to this file may cause incorrect behavior and will be lost if
//      the code is regenerated.
//  </auto-generated>
// ------------------------------------------------------------------------------
#region Designer generated code
namespace xxx.xxx.xxx
{
    using TechTalk.SpecFlow;
    
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow""1.7.0.0")]
    [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()]
    public partial class GetAllArticlesFromRepositoryFeature
    {
        
        private static TechTalk.SpecFlow.ITestRunner testRunner;
        
#line 1 "GetAllArticles.feature"
#line hidden
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()]
        public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext)
        {
            testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
            TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Get all articles from Repository""In order to get all articles\r\nAs a cms user\r\nSo that I can get and process in all" +
                    " articles"ProgrammingLanguage.CSharp, ((string[])(null)));
            testRunner.OnFeatureStart(featureInfo);
        }
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()]
        public static void FeatureTearDown()
        {
            testRunner.OnFeatureEnd();
            testRunner = null;
        }
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()]
        public virtual void TestInitialize()
        {
            if (((TechTalk.SpecFlow.FeatureContext.Current != null
                        && (TechTalk.SpecFlow.FeatureContext.Current.FeatureInfo.Title != "Get all articles from Repository")))
            {
                xxx.xxx.xxx.GetAllArticlesFromRepositoryFeature.FeatureSetup(null);
            }
        }
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()]
        public virtual void ScenarioTearDown()
        {
            testRunner.OnScenarioEnd();
        }
        
        public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
        {
            testRunner.OnScenarioStart(scenarioInfo);
        }
        
        public virtual void ScenarioCleanup()
        {
            testRunner.CollectScenarioErrors();
        }
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
        [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get All articles")]
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle""Get all articles from Repository")]
        public virtual void GetAllArticles()
        {
            TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get All articles"new string[] {
                        "GetAllArticles"});
#line 7
this.ScenarioSetup(scenarioInfo);
#line 8
 testRunner.Given("I don\'t input any parametters");
#line 9
 testRunner.When("I call the GetAllArticle function from service");
#line 10
 testRunner.Then("the result should be a list of article");
#line hidden
            this.ScenarioCleanup();
        }
        
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
        [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get All articles with error")]
        [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle""Get all articles from Repository")]
        public virtual void GetAllArticlesWithError()
        {
            TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get All articles with error"new string[] {
                        "GetAllArticlesWithError"});
#line 13
this.ScenarioSetup(scenarioInfo);
#line 14
 testRunner.Given("I dont input any parametters");
#line 15
 testRunner.When("I call the GetAllArticle function from service");
#line 16
 testRunner.Then("the result should be a exception");
#line hidden
            this.ScenarioCleanup();
        }
    }
}
#endregion

Look like terrible and hard to understand, itsn't it? No problem, don't care about it. It will be fine Don't scare it :)

The first thing, I try to write some interface to make a skeleton for solution

    public interface IArticleRepository : IRepositoryEntityBase<Article>
    {
        ...
    }
    public interface IArticleCategoryRepository : IRepositoryEntityBase<ArticleCategory>
    {

    }
    public interface IArticleInfoRepository : IRepositoryEntityBase<ArticleInfo>
    {
        ...
    }
    
    public interface IArticleContentRepository : IRepositoryEntityBase<ArticleContent>
    {
        ....}

     

    public interface IArticleMediaItemRepository : IRepositoryEntityBase<MediaItem>
    {
        
    }

and an interface for article manager

    public interface IArticleManager
    {

        }

Certainly, we should have article manager instance class

    public class ArticleManager : IArticleManager
    {
        private readonly IArticleContentRepository _articleContentRepository;
        private readonly IArticleInfoRepository _articleInfoRepository;
        private readonly IArticleRepository _articleRepository;
        private readonly IAuthorInfoRepository _authorInfoRepository;
        private readonly IArticleCategoryRepository _categoryRepository;
        private readonly IPublishingArticleRepository _publishingArticleRepository;
        private readonly IReferenceRepository _referenceRepository;
        private readonly IArticleMediaItemRepository _articleMediaItemRepository;

        
        public ArticleManager(IArticleRepository articleRepository
                              , IArticleCategoryRepository categoryRepository
                              , IArticleContentRepository articleContentRepository
                              , IReferenceRepository referenceRepository
                              , IArticleInfoRepository articleInfoRepository
                              , IAuthorInfoRepository authorInfoRepository
                              , IPublishingArticleRepository publishingArticleRepository
                              , IArticleMediaItemRepository articleMediaItemRepository)
        {
            _articleRepository = articleRepository;
            _authorInfoRepository = authorInfoRepository;
            _articleInfoRepository = articleInfoRepository;
            _referenceRepository = referenceRepository;
            _articleContentRepository = articleContentRepository;
            _categoryRepository = categoryRepository;
            _publishingArticleRepository = publishingArticleRepository;
            _articleMediaItemRepository = articleMediaItemRepository;
        }

      ....................

     }

Note: I use Unity for dependency injection framework, but I will not mention it this post, because it is very easy to achive.

After that I add 2 step definitions for get all article and get all article with an exception should be throw out

    [Binding]
    public class GetAllArticleSteps : AutoMockObject
    {
        private IArticleManager _articleManager;
        private IEnumerable<Article> _result;

        [Given(@"I don't input any parametters")]
        [StepScope(Tag = "GetAllArticles")]
        public void GivenIDontInputAnyParamatters()
        {
            Register<IArticleRepository>(mock =>
                                             {
                                                 mock.Setup(framework => framework.GetAll()).Returns(GetAllArticleStub.ArticleList);
                                                 mock.Setup(framework => framework.GetKeywordsByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.KeywordList);
                                                 mock.Setup(framework => framework.GetTopicsByArticleId(It.IsAny<int>(), false)).Returns(GetAllArticleStub.TopicList);
                                                 mock.Setup(framework => framework.GetTopicsByArticleId(It.IsAny<int>(), true)).Returns(GetAllArticleStub.TopicList);
                                             });

            Register<IArticleCategoryRepository>(mock => mock.Setup(framework => framework.GetById(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleCategory));
            Register<IArticleInfoRepository>(mock => mock.Setup(framework => framework.GetArticleInformationById(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleInformation));
            Register<IAuthorInfoRepository>(mock => mock.Setup(framework => framework.GetAuthorInformationById(It.IsAny<int>())).Returns(GetAllArticleStub.AuthorInformation));
            Register<IArticleContentRepository>(mock => mock.Setup(framework => framework.GetSummaryByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleContentList));
            Register<IArticleMediaItemRepository>(mock =>
                                                      {
                                                          mock.Setup(framework => framework.GetArticleMediaItemByArticleId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleMediaItemList);
                                                          mock.Setup(framework => framework.GetArticleMediaItemAltTextByArticleMediaItemId(It.IsAny<int>())).Returns(GetAllArticleStub.ArticleMediaItemAltTextList);
                                                      });

            _articleManager = new ArticleManager(
                    Resolve<IArticleRepository>(),
                    Resolve<IArticleCategoryRepository>(),
                    Resolve<IArticleContentRepository>(),
                    Resolve<IReferenceRepository>(),
                    Resolve<IArticleInfoRepository>(),
                    Resolve<IAuthorInfoRepository>(),
                    Resolve<IPublishingArticleRepository>(),
                    Resolve<IArticleMediaItemRepository>()
                );
        }

        [When(@"I call the GetAllArticle function from service")]
        [StepScope(Tag = "GetAllArticles")]
        public void WhenICallTheGetAllArticleFunctionFromTheService()
        {
            _result = _articleManager.GetAllArticle();
        }

        [Then(@"the result should be a list of article")]
        [StepScope(Tag = "GetAllArticles")]
        public void ThenTheResultShouldBe()
        {
            Assert.IsNotNull(_result);
            Assert.IsTrue(_result.Count() == 1);
        }
    }
   
    [Binding]
    public class GetAllArticleWithErrorSteps
    {
        private IArticleManager _articleManager;
        private IEnumerable<Article> _result;

        [Given(@"I dont input any parametters")]
        [StepScope(Tag = "GetAllArticlesWithError")]
        public void GivenIDontInputAnyParamettersWithError()
        {
            _articleManager = new ArticleManager(nullnullnullnullnullnullnullnull);
        }

        [When(@"I call the GetAllArticle function from service")]
        [StepScope(Tag = "GetAllArticlesWithError")]
        public void WhenICallTheGetAllArticleFunctionFromServiceWithError()
        {
            try
            {
                _result = _articleManager.GetAllArticle();
            }
            catch (Exception e)
            {
                ScenarioContext.Current.Add("NullException_GetAllArticleMethod", e);
            }
        }

        [Then("the result should be a exception")]
        [StepScope(Tag = "GetAllArticlesWithError")]
        public void ThenTheResultShouldBe()
        {
            Assert.IsTrue(ScenarioContext.Current.ContainsKey("NullException_GetAllArticleMethod"));
        }
    }

 This is just a simple scenario for demo. Nothing is complex here.  

 Now try to run all unit testings inside Visual Studio 2010, it will be can run because you don't have an implementation for article business class. Now we add some code for it.

        public IEnumerable<Article> GetAllArticle()
        {
            IEnumerable<Article> articles = _articleRepository.GetAll();

            foreach (var article in articles)
            {
                SetReferenceData(article);
            }

            return articles;
        } 
        private void SetReferenceData(Article article)
        {
            Guard.ArgumentNotNull(article, "article");

            article.Category = _categoryRepository.GetById(article.CategoryId);
            article.ArticleInformation = _articleInfoRepository.GetArticleInformationById(article.ArticleInformationId);
            article.AuthorInformation = _authorInfoRepository.GetAuthorInformationById(article.AuthorInformationId);

            article.Keywords = _articleRepository.GetKeywordsByArticleId(article.ArticleId).ToList();
            article.Topics = _articleRepository.GetTopicsByArticleId(article.ArticleId, false).ToList();
            article.SubTopics = _articleRepository.GetTopicsByArticleId(article.ArticleId, true).ToList();

            List<ArticleContent> summaryContent = _articleContentRepository.GetSummaryByArticleId(article.ArticleId).ToList();
            if (summaryContent.Count > 1)
            {
                var baseContent = summaryContent.Find(ac => ac.LocaleCode == article.LocaleCode);
                summaryContent.Remove(baseContent);
                summaryContent.Insert(0, baseContent);
            }
            article.ArticleContents = summaryContent;

            var mediaItems = _articleMediaItemRepository.GetArticleMediaItemByArticleId(article.ArticleId);

            foreach (var articleMediaItem in mediaItems)
            {
                articleMediaItem.ArticleMediaItemAltTexts = _articleMediaItemRepository.GetArticleMediaItemAltTextByArticleMediaItemId(
                                                                                                        articleMediaItem.ArticleMediaItemId);
            }

            article.MediaItems = mediaItems.ToList();
        }

Make sure you add all methods for all repositories, try to run test again. And now it pass. How about your thinking? All comment for discussion are welcome. Happy coding and see you next post.

13 Comments

  • Hi a.Thoai,

    I just want to clarify a little bit:

    + Unit Tests - Objects in isolation behave in the proper manner. (Mock, Stub, Fake,...)

    + Acceptance/Integration Tests - All Objects together provide the promised value of the system. (BDD here)

    I had some mistakes when I wrote it look like Unit testing, not behaviour testing. In my context, my company applied SOA, so I just want to use BDD for testing from Service layer to Repository (maybe communicating directly with database). So the code in my post is not correct for this context, it is just a simple sample for how to apply BDD in my system.

    Thank for your comments. It is really good experience for me. Your blog is really interesting in :D

  • I always wonder to get such type of useful and practical articles, but to be very frank, these articles are rarely available which has the effectiveness up to the mark. I am very impressed by the article. Great info in a great manner.

  • For the reason that the admin of this web page is working, no question very rapidly
    it will be renowned, due to its quality contents.

  • It's an amazing paragraph for all the internet people; they will obtain benefit from it I am sure.

  • I am now not sure where you're getting your info, but good topic. I needs to spend a while learning much more or working out more. Thanks for excellent info I used to be looking for this information for my mission.

  • I enjoy reading a post that can make men and women think.

    Also, thanks for permitting me to comment!

  • I don't even know how I ended up here, but I thought this post was good. I don't know who you are but certainly you're going to a famous blogger if you aren't already ;) Cheers!

  • Hi there, just became alert to your blog through Google, and found that it
    is really informative. I am gonna watch out for brussels.
    I'll be grateful if you continue this in future. Numerous people will be benefited from your writing. Cheers!

  • I'm truly enjoying the design and layout of your blog. It's
    a very easy on the eyes which makes it much more pleasant for me to come here and visit
    more often. Did you hire out a designer to create your theme?
    Outstanding work!

  • I'm not sure exactly why but this website is loading very slow for me. Is anyone else having this issue or is it a problem on my end? I'll check back later on and see if the problem still exists.

  • I couldn't resist commenting. Very well written!

  • What's up to all, it's truly a good for me to pay a visit this
    website, it consists of priceless Information.

  • You can definitely see your expertise in the article you write.
    The sector hopes for more passionate writers such as you who aren't afraid to say how they believe. All the time go after your heart.

Comments have been disabled for this content.