Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Fluent API in bddify

, 11 Jan 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
This is the fifth article in the 'Bddify In Action' series. It is recommended to read this series in the presented order because I use some of the references provided in the previous articles.

This is the fifth article in the 'Bddify In Action' series. It is recommended to read this series in the presented order because I use some of the references provided in the previous articles.

The code used in this article is available for download from here.

Bddify can scan your tests in one of two ways: using Reflective API and Fluent API. Reflective API uses some hints to scan your classes. These hints are provided through Method Name Conventions and/or ExecutableAttribute which we have discussed before. For this post, we will concentrate on Fluent API.

I just thought I would share a bit of history with you first. I had just released bddify V0.5 and the API had kinda settled. So I thought I'd write an introductory article on CodeProject to promote the framework. At the top of article, I said 'bddify is very extensible. In fact, bddify core barely has any logic in it. It delegates all its responsibilities to its extensions'. Then I thought that just claiming a framework is extensible does not mean anything if I cannot provide a sample for it. That is why I wrote the Fluent API as I was writing that article to prove it to myself that bddify is highly extensible and also to provide an example of that. Well, I started it as an extensibility example; but then I liked and felt the need for it and baked into the framework. Today it is no longer a sample and in fact it is even more popular than Reflective mode!!

Fluent API

Fluent API of bddify does not really require much explanation as it is quite fluent Wink | ;-) So instead of trying to explain to you how it works, I will just provide an example.

In the Method Name Conventions post, I wrote a scenario called 'BddifyRocks' which I repeat here for your convenience:

public class BddifyRocks
{
    [Test]
    public void ShouldBeAbleToBddifyMyTestsVeryEasily()
    {
        this.Bddify();
    }

    void GivenIHaveNotUsedBddifyBefore()
    {
    }

    void WhenIAmIntroducedToTheFramework()
    {
    }

    void ThenILikeItAndStartUsingIt()
    {
    }
}

And then we expanded that scenario to the second one shown below:

public class BddifyRocksEvenForBddNewbies
{
    [Test]
    public void ShouldBeAbleToBddifyMyTestsVeryEasily()
    {
        this.Bddify();
    }

    void GivenIAmNewToBdd()
    {
    }

    void AndGivenIHaveNotUsedBddifyBefore()
    {
    }

    void WhenIAmIntroducedToTheFramework()
    {
    }

    void ThenILikeItAndStartUsingIt()
    {
    }

    void AndILearnBddThroughBddify()
    {
    }
}

Let's rewrite these two scenarios using Fluent API:

using NUnit.Framework;

namespace Bddify.FluentApi
{
    public class BddifySeriouslyRocks
    {
        [Test]
        public void BddifyRocks()
        {
            this.Given(_ => GivenIHaveNotUsedBddifyBefore())
                .When(_ => WhenIAmIntroducedToTheFramework())
                .Then(_ => ThenILikeItAndStartUsingIt())
                .Bddify();
        }

        [Test]
        public void BddifyEvenRocksForBddNewbies()
        {
            this.Given(_ => GivenIAmNewToBdd())
                    .And(_ => AndIHaveNotUsedBddifyBefore())
                .When(_ => WhenIAmIntroducedToTheFramework())
                .Then(_ => ThenILikeItAndStartUsingIt())
                    .And(_ => AndILearnBddThroughBddify())
                .Bddify();
        }

        void GivenIHaveNotUsedBddifyBefore()
        {
        }

        void GivenIAmNewToBdd()
        {
        }

        void AndIHaveNotUsedBddifyBefore()
        {
        }

        void WhenIAmIntroducedToTheFramework()
        {
        }

        void ThenILikeItAndStartUsingIt()
        {
        }

        void AndILearnBddThroughBddify()
        {
        }
    }
}

This class has two test methods each representing one of the scenarios. The reports generated by these tests are the same as those shown in the Method Name Conventions post. As seen below, the only difference is the name of the assembly and the namespace (which are different on purpose):

Console report without story

There are a few important differences in implementation as follows:

  • In Reflective API, the only thing you need to call is this.Bddify(); or one of its overloads that accepts the story type argument and/or the custom scenario title (and then bddify will find your steps using conventions or attributes). In the Fluent API, you should explicitly specify all your steps before calling the Bddify method.
  • When using Reflective API, the scenario name is driven by the name of the class because each class represents a scenario. In Fluent API, however, a class usually represents a story (or collection of related scenarios in the absence of a story) and scenarios are represented by methods. That is why while porting the sample to use Fluent API, I renamed my scenario method names to match the class name that represented the scenario in the source sample. This is to ensure that I will get the same title for my scenarios after using Fluent API.
  • In Reflective API, bddify would pick up any combination of scenario steps by method name conventions and ExecutableAttribute; but in Fluent API mode, you are in complete control. This means that regardless of what your method names are or whether they are decorated with ExecutableAttribute or not, the steps you specify using the Fluent API will run by bddify. Likewise if there is a method that complies with method name conventions and/or is decorated by ExecutableAttribute (or one of its derivatives) but is not specified in your Fluent API call, it is not going to be picked up by the framework. Reflective and Fluent modes run in isolation of each other and you choose the mode by the way you call the Bddify method.
  • In Reflective mode, the method name starting with 'AndGiven' and 'AndWhen' will result into steps starting with 'And': the framework knows that you have provided the extra 'Given' and 'When' words only to comply with its conventions and as such drops them from the reports. In the Fluent API, your step titles are derived directly from your method name. So when porting the example from using Method Name Convention to Fluent API, I renamed AndGivenIHaveNotUsedBddifyBefore to AndIHaveNotUsedBddifyBefore to avoid getting 'And given' in my report.
  • You notice that I removed two methods while porting the code to use Fluent API: WhenIAmIntroducedToTheFramework and ThenILikeItAndStartUsingIt. These two methods were repeated in each scenario; but I ported all scenarios and methods to the same class; so we can avoid duplication. Well, in all fairness the same could be achieved in the Reflective mode through inheritance where the shared logic lives in a base class that other scenarios subclass; but I think the reuse is kinda more natural in the Fluent mode.
  • If you use R#, in Reflective mode if you write your steps as private methods, you are going to get R# warning for unused methods because R# does not have any idea about the reflection magic going behind the scenes. Using Fluent API because you explicitly call the methods, you no longer get the R# warning because you are using the methods. In order to avoid the warning in Reflective mode, you may define your methods as protected or public to avoid the warnings.

Adding Story

Out of the box, there is only one way to specify your Story and to associate it with scenarios and that is using StoryAttribute. This is the same for Reflective and Fluent modes.

Let's add story to the above example:

using Bddify.Core;
using NUnit.Framework;

namespace Bddify.FluentApi
{
     [Story(
        AsA = "As a .net programmer",
        IWant = "I want to use bddify",
        SoThat = "So that BDD becomes easy and fun")]
    public class BddifySeriouslyRocks
    {
        [Test]
        public void BddifyRocks()
        {
            this.Given(_ => GivenIHaveNotUsedBddifyBefore())
                .When(_ => WhenIAmIntroducedToTheFramework())
                .Then(_ => ThenILikeItAndStartUsingIt())
                .Bddify();
        }

        // The rest is removed for brevity
    }
}

As mentioned in a previous post, to create a story, you need to decorate a class with StoryAttribute. In the Fluent mode, the story class is usually the same as the class that contains the scenarios because, unlike Reflective mode, a scenario does not necessarily map to a class and is usually implemented in a method.

This of course has its pros and cons. The nice thing about this approach is that you can see an entire story in one file/class; at the same time that could be considered a disadvantage because some stories are rather big and have quite a few scenarios which will result into a big class. Again, you do not have to put all the scenarios of a story in one class: that is just one option.

Running the tests now will include the story title and narrative into console and HTML reports.

FAQ

These are some of the FAQs I have received for Fluent API:

Should I have my methods in the right order?

In the Reflective mode, there is a situation where you have to put your methods in the right order and that is when you have more than one 'AndGiven' or 'AndWhen' or 'And' in which case the 'and' parts are executed in the order they appear in the class. In the Fluent API, that does not matter. The methods are executed in the order specified using the Fluent API. So it does not matter in what order they appear in the class.

How I can reuse some of the testing logic?

As mentioned above with Fluent API, it is very easy to reuse the test logic across all scenarios of the same story because usually they are all in the same class. If the logic is not in the same class, you can still use inheritance or composition to compose a scenario.

Can my step methods be static or should they be instance methods?

Bddify handles both cases. So feel free to use whatever makes sense.

Conclusion

In the last few posts, I talked about Method Name Conventions and ExecutableAttribute. In this post, we saw Fluent API - the last of built-in bddify scanners. These are the scanners that come out of the box with bddify.

Bddify is quite customizable and extensible. You can very easily create your own dialect using Method Name Conventions and Executable Attributes and, as mentioned above, Fluent API was born out of an example and took only a few hours to implement (I have a few upcoming posts dedicated to customizing the framework.). So if you think the out of the box scanners do not behave the way you expect, you may either customize them or very easily create your own. If you do so, I would appreciate if you could share your experience (and/or code) with me Blush | :O )

The code used in this article is available for download from here.

Hope this helps.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mehdi Khalili
Software Developer (Senior) ThoughtWorks
United States United States
I work as a Senior Consultant for ThoughtWorks
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 11 Jan 2012
Article Copyright 2012 by Mehdi Khalili
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid