Over a million developers have joined DZone.

Gherkin style specification testing in .NET

·

Behavior Driven Development is something that has interested me for quite awhile. I have constantly tried to write my tests as clear and concise as possible but once I saw Cucumber for Ruby that became the new standard for what I was trying to achieve in .NET. This is where SpecFlow comes in.

SpecFlow is a BDD library for .NET that aims to add testing capabilities that are similar to Cucumber -- that is, specifications are written in human readable Gherkin Format. From the project site

SpecFlow aims at bridging the communication gap between domain experts and developers by binding business readable behavior specifications to the underlying implementation.

In theory, I really like that domain experts could write the specifications but I would be interested in seeing how that works out.

So what exactly is this Gherkin format?

According to the Gherkin project on github, 'Gherkin is the language that Cucumber understands. It is a Business Readable, Domain Specific Language that lets you describe software’s behaviour without detailing how that behaviour is implemented.' In other words, its a common DSL for describing the required functionality for a given system. 

This functionality is typically broken down by feature and each feature has a number of scenarios. A scenario is made up of 3 steps: GIVEN, WHEN and THEN (which seems to somewhat loosely correspond to Arrange, Act, Assert) and in a simplistic world, looks a little like this:

GIVEN an admin user
WHEN user requests top secret data
THEN return the list of data
If you want to learn more about the Gherkin format check out Engine Yard's Introduction to BDD with Cucumber by Dave Astels or Given-When-Then by Aslak Hellesøy

Quick Synopsis

I've recently started to move my blog over to a new server and a new root domain name; this could have an adverse affect on inbound links. In order to make sure this move was successful, I wanted to write an app to perform 301 redirects from the old URL to the new one.

There are a number of examples out there already for performing 301s but I wanted to make sure I was testing the code -- It seemed like a great opportunity to get a little more use out of SpecFlow.

Initial Setup

  1. Download and run the SpecFlow installer
  2. Create a new Project and add a reference to SpecFlow and NUnit Framework
  3. Add references to your mocking framework (this example is using Moq)

On with the code!

After all the references are sorted out, add a SpecFlow feature.



The feature file is where we're going to define our specifications. I want to make sure that when a request is made to the old root it will get redirect to the new root url. So here is what the feature looks like initially:

Feature: Redirection
    In order to not upset the google
    As a blogger who almost never has the time to blog
    I want to redirect my old url to my new one

Scenario: Redirect root request
    Given I have entered a request to http://www.frickinsweet.com/ryanlanciaux.com
    And the old url is frickinsweet.com/ryanlanciaux.com
    And my new url is ryanlanciaux.com
    When the request is made
    Then the response url is http://www.ryanlanciaux.com
    And the response has a 301 in the status

Notice that over in the Solution Explorer window you can expand the feature to reveal a .cs file.

The class is an auto-generated file that updates when the .feature file is changed. We can run this through our test runner to watch it fail and get some extra information as to why it failed.

 



The tests are failing because there is no real definition to the scenario steps. We can almost directly copy and paste the output from the test runner dialog to a new class and fill in the code for the methods with standard unit testing code. Like I said before, I treat everything that is a GIVEN statement like an arrange section of a standard test; WHEN and THEN like arrange and assert respectively.

We could hardcode these tests to be specifically run against the urls specified in the scenario but this approach feels brittle and does not encourage code reuse. In order to use these these same steps in our future scenarios we can add wildcard mappings rather than specifying a single url in the attribute definition. The wildcard mapping is the familiar .* surrounded by parenthesis.

   24         [Given(@"I have entered a request to (.*)")]

Also note now that when we have a wildcard mapping, we can pass in a parameter to that ScenarioStepDefinition

public void GivenIHaveEnteredARequestToHttpWww_Frickinsweet_ComRyanlanciaux_ComPage2(string url)

The final result is a lot of code but it is broken down into small, reusable sections.

[TestFixture]
[Binding]
public class RedirectTest
{
private string oldUrl;
private string newUrl;
private string requestedUrl;
private string finalUrl;

private RedirectHandler _handler;
private Mock<HttpContextBase> mockContext;
private Mock<HttpResponseBase> mockResponse;


[Given(@"I have entered a request to (.*)")]
public void GivenIHaveEnteredARequestToHttpWww_Frickinsweet_ComRyanlanciaux_ComPage2(string url)
{
var uri = new Uri(url);
requestedUrl = url;

mockContext = new Mock<HttpContextBase>();
mockContext.Setup(x => x.Request.Url).Returns(uri);

}

[Given(@"the old url is (.*)")]
public void GivenTheOldUrlIsFrickinsweet_ComRyanlanciaux_Com(string url)
{
oldUrl = url;
}

[Given(@"my new url is (.*)")]
public void GivenMyNewUrlIsRyanlanciaux_Com(string url)
{
newUrl = url;

//now that we know both old and new url do a replace on httpcontexts' url
//setup what we expect the called url to be and throw a callback on the mock so we can verify later
mockResponse = new Mock<HttpResponseBase>();
mockResponse.SetupProperty(x => x.Status);
mockResponse.Setup(x => x.AddHeader("Location", requestedUrl.Replace(oldUrl, newUrl)))
.Callback(() => finalUrl = requestedUrl.Replace(oldUrl, newUrl));

mockContext.Setup(x => x.Response).Returns(mockResponse.Object);

}

[When(@"the request is made")]
public void WhenTheRequestIsMade()
{
_handler = new RedirectHandler();
_handler.ProcessRequest(mockContext.Object, oldUrl, newUrl);
}

[Then(@"the response has a 301 in the status")]
public void ThenTheResponseHasA301InTheStatus()
{
Assert.That(mockContext.Object.Response.Status == "301 Moved Permanently");
}

[Then(@"the response url is (.*)")]
public void ThenTheResponseUrlIsTheNewUrl(string expectedUrl)
{
Assert.AreEqual(expectedUrl, finalUrl);
}

[Then(@"301 is not in the headers")]
public void Then_301IsNotInTheHeaders()
{
Assert.IsNull(mockResponse.Object.Status);
}
}

Since we are using wildcards instead of raw urls in the step definitions we can easily write other tests that will just work with out adding any extra code.

Scenario: Redirect to correct path on new url
    Given I have entered a request to http://www.frickinsweet.com/ryanlanciaux.com/page2
    And the old url is frickinsweet.com/ryanlanciaux.com
    And my new url is ryanlanciaux.com
    When the request is made
    Then the response url is http://www.ryanlanciaux.com/page2
    And the response has a 301 in the status

This project, in its entirety, is hosted on GitHub. Check it out if you are interested in seeing SpecFlow in the context of the whole (tiny) application. Make sure that you add all the files from the lib dir into your references the first time you run it or you will receive all kinds of errors -- additionally, I wrote this quickly for myself so there is no real warranty / guarantee that the code is free from defects -- use at your own risk. :)

Download Project from GitHub

Visit the SpecFlow homepage 

 

Topics:

Published at DZone with permission of Ryan Lanciaux. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}