Fluent API in BDDfy
[Update - 2013-08-10] BDDfy is now part of TestStack organization. So this article has been ported to and is superseded by this post on the documentation website.
This is the fifth article in the ‘BDDfy 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.
BDDfy 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][4]
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 BDDfy V0.5 and the API had kinda settled. So I thought I’d write an introductory article on CodeProject to promote the framework. On the top of article I said ‘BDDfy is very extensible. In fact, BDDfy 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 BDDfy 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 BDDfy does not really require much explanation as it is quite fluent ;-) 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 ‘BDDfyRocks’ which I repeat here for your convenience:
public class BDDfyRocks { [Test] public void ShouldBeAbleToBDDfyMyTestsVeryEasily() { this.BDDfy(); } void GivenIHaveNotUsedBDDfyBefore() { } void WhenIAmIntroducedToTheFramework() { } void ThenILikeItAndStartUsingIt() { } }
And then we expanded that scenario to the second one shown below:
public class BDDfyRocksEvenForBddNewbies { [Test] public void ShouldBeAbleToBDDfyMyTestsVeryEasily() { this.BDDfy(); } void GivenIAmNewToBdd() { } void AndGivenIHaveNotUsedBDDfyBefore() { } void WhenIAmIntroducedToTheFramework() { } void ThenILikeItAndStartUsingIt() { } void AndILearnBddThroughBDDfy() { } }
Let’s rewrite these two scenarios using Fluent API:
using NUnit.Framework; namespace BDDfy.FluentApi { public class BDDfySeriouslyRocks { [Test] public void BDDfyRocks() { this.Given(_ => GivenIHaveNotUsedBDDfyBefore()) .When(_ => WhenIAmIntroducedToTheFramework()) .Then(_ => ThenILikeItAndStartUsingIt()) .BDDfy(); } [Test] public void BDDfyEvenRocksForBddNewbies() { this.Given(_ => GivenIAmNewToBdd()) .And(_ => AndIHaveNotUsedBDDfyBefore()) .When(_ => WhenIAmIntroducedToTheFramework()) .Then(_ => ThenILikeItAndStartUsingIt()) .And(_ => AndILearnBddThroughBDDfy()) .BDDfy(); } void GivenIHaveNotUsedBDDfyBefore() { } void GivenIAmNewToBdd() { } void AndIHaveNotUsedBDDfyBefore() { } void WhenIAmIntroducedToTheFramework() { } void ThenILikeItAndStartUsingIt() { } void AndILearnBddThroughBDDfy() { } } }
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):
There are a few important differences in implementation as follows:
- In Reflective API the only thing you need to call is
this.BDDfy();
or one of its overloads that accepts the story type argument and/or the custom scenario title (and then BDDfy will find your steps using conventions or attributes). In the Fluent API you should explicitly specify all your steps before calling theBDDfy
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 BDDfy 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 withExecutableAttribute
or not the steps you specify using the Fluent API will run by BDDfy. Likewise if there is a method that complies with method name conventions and/or is decorated byExecutableAttribute
(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 theBDDfy
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
AndGivenIHaveNotUsedBDDfyBefore
toAndIHaveNotUsedBDDfyBefore
to avoid getting ‘And given’ in my report. - You notice that I removed two methods while porting the code to use Fluent API:
WhenIAmIntroducedToTheFramework
andThenILikeItAndStartUsingIt
. 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 BDDfy.Core; using NUnit.Framework; namespace BDDfy.FluentApi { [Story( AsA = "As a .net programmer", IWant = "I want to use BDDfy", SoThat = "So that BDD becomes easy and fun")] public class BDDfySeriouslyRocks { [Test] public void BDDfyRocks() { this.Given(_ => GivenIHaveNotUsedBDDfyBefore()) .When(_ => WhenIAmIntroducedToTheFramework()) .Then(_ => ThenILikeItAndStartUsingIt()) .BDDfy(); } // 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 you 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?
BDDfy 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 BDDfy scanners. These are the scanners that come out of the box with BDDfy.
BDDfy 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 uncoming 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 :o)
The code used in this article is available for download from here.
Hope this helps.