Integration Testing in a Development Environment With SpecFlow — Part 1: Cassini

When developing web applications on anything other than a
trivial scale, anybody who is anybody in the community will likely tell you
that testing is an absolute must. Their rationales will be hard to argue and
probably along the lines of:

·
You can refactor with confidence

·
You have documentation for your code

·
You know what works and what still needs working on

·
You will be alerted of any breaking changes you make

Integration testing tests
your entire stack working together
. But when this is a web application, and
you are in a development environment, you need a web server for an accurate simulation.
So in this series of blog posts, I’ll be showing what I know about, and what I
use for, integration testing.

In this particular post, we’ll be looking at the Cassini web
server.

Using Cassini

Enabling integration tests in a development environment was
first presented to me in the book Testing
ASP.NET Web Applications
. So any code shown that involves Cassini will be
based on what I learnt from this useful book.

Implementing Cassini

To demonstrate using Cassini, I will create a demo ASP.NET
MVC 3 beta
application. As part of this I’ll be using Watin, NUnit
and obviously SpecFlow.

Important:
If you plan to use this approach then please read the conclusion — there is
important information you definitely need to know.

Let’s get started:

Creating the Environment

1. Create
an ASP.NET MVC 3 application — specifying your favourite view engine (I’ll use
Razor — but I love Spark) and choose internet application as opposed to empty
(so we don’t have to do our own CSS).

2. Now
add a separate class library for our specs. Following the convention of a real
project, let’s call it Specs.Integration.

3. Now
add your references to Nunit, Watin & SpecFlow in Specs.Integration (I used
the awesome NuPack
for Watin. Nunit is on there, too).

4. At
this point we need to add a reference to Cassini.dll in Specs.Integration AND
the web project. If you don’t have this dll on your machine, then you can get
it as part of the Spark view engine
download (and learn a real view engine at the same time).

5. To
get Watin working, we’ll also need to add some configuration. This is easily
achieved by adding an xml file called App.config to the root of
Specs.Integration consisting of the following markup:

Setting up an Imaginary Scenario

For this stage, I’m just going to add an overload to the
Home/Index action that accepts a string. If that string is “WebSecurityEssentials”
then we will be redirected to the OWASP website.

The Specs

Based on what I’ve just said, the specs will be pretty
obvious. So by adding a SpecFlow feature file to Specs.Integration, we can document
our application’s only requirement.

Here is that feature file, which I’ve named “SpecialHomePageInputValuePerformsSpecialRedirect.feature”:

Feature:
Special Home Page Input Value Performs Special Redirect

In
order to demonstrate integration testing with SpecFlow

As
a blogger

I
need to redirect to a secret web site when the secret value is entered

Scenario:
User Enters Secret Value on Home Page

Given
I am viewing the home page

When
I enter WebSecurityEssentials in the special value text box and submit the form

Then
I should be redirect to www.OWASP.org

With some pretty solid specs, we can now add our step definitions
which should be quite easy. For now, the use of a web server will be ignored.

Adding the steps is easy: add a new SpecFlow step file to
Specs.Integration naming it identically to the feature file (excluding file
extension). After doing that myself, I pointed to Specs.Integration and hit “run
unit tests” on the contexts menu. In the testing window, I then get the
un-implemented step definitions I can copy and past into my step file.

Having implemented the steps, here’s what I now have:

using
NUnit.Framework;

using
TechTalk.SpecFlow;

using
WatiN.Core;

namespace
Specs.Integration.Steps

{

[Binding]

public class SpecialHomePageInputValuePerformsSpecialRedirect

{

private IE
_browser;

private const string SpecialInputTextBoxID = “abcdefg”;

private const string HomePageUrl = @”http://localhost:8090/Home/Index";

public SpecialHomePageInputValuePerformsSpecialRedirect()

{

_browser = new IE();

}

[Given(@”I
am viewing the home page”)]

public void
GivenIAmViewingTheHomePage()

{

_browser.GoTo(HomePageUrl);

}

[When(@”I
enter (.*) in the special value checkbox and submit the form”)]

public void
WhenIEnterWebSecurityEssentialsInTheSpecialValueCheckbox(string secretInputValue)

{

var specialInputTextBox =
_browser.TextField(SpecialInputTextBoxID);

specialInputTextBox.TypeText(secretInputValue);

_browser.Buttons.First().Click();

}

[Then(@”I
should be redirect to www\.OWASP\.org”)]

public void
ThenIShouldBeRedirectToWww_OWASP_Org()

{

var currentBrowserUrl = _browser.Url;

// sometimes browsers and url casings can mess up tests — lets go ToUpper()
to remove any potential issues

Assert.IsTrue(currentBrowserUrl.ToUpper().Contains(“WWW.OWASP.ORG"), “We were not redirected to the OWASP web site”);

}

}

}

Obviously, none of this will work because the main
application is still the un-touched default MVC 3 beta web site. Also, there is
still no web server. However, let’s just tackle the first issue: add a form,
text box (id & name specified in step file) and submit button to the
Home/Index view. Then add the overload to the Home/Index action that performs
the redirect if the required value is submit. I won’t show this code, but just
trust me that it works.

Setting up the Web Server — Cassini

My specs still cannot be tested because the application is
not being hosted by a web server. Sure I could start it up in Visual Studio,
but that’s not going to help integration testing. So to use Cassini
programmatically to host my application, I’ll add a static class to
Specs.Integration (explain why shortly) that can be used to start and stop
Cassini and to also specify which site to host.

Here is that class, and below it is an explanation of its
workings:

using
System;

using
System.Diagnostics;

using
System.IO;

using
Cassini;

using
TechTalk.SpecFlow;

namespace
Specs.Integration

{

[Binding]

public static class WebServerStepsEnvironmentManager

{

private static Server _webServer;

public static string ServerUrl

{

get { return String.Format(“http://localhost:{0}/",
ServerPort); }

}

public static string ServerPhysicalPath

{

get { return new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.Parent.FullName
+ “\\SpecFlowIntegrationTesting”;}

}

public static int ServerPort { get
{ return 8090; } }

[BeforeFeature(new[]
{“WebServer”})]

public static void Setup()

{

string virtualDirectory = “/”;

try

{

_webServer
= new Server(ServerPort,
virtualDirectory, ServerPhysicalPath);

_webServer.Start();

Debug.WriteLine(String.Format(“Started Port:{0} VD:{1} PD{2}”,
ServerPort, virtualDirectory, ServerPhysicalPath));

}

catch (Exception
ex)

{

Debug.WriteLine(String.Format(“Error starting web service” +
ex.Message));

}

}

[AfterFeature(new[]{“WebServer”})]

public static void TearDown()

{

try

{

if (_webServer != null)

{

_webServer.Stop();

_webServer
= null;

}

}

catch (Exception
ex)

{

Debug.WriteLine(“Tear
down error: “ + ex.Message);

}

}

}

}

The four important components above are the
ServerPhysicalPath which needs to point to the folder containing the web site
you want to load, the ServerPort which you can correlate in web.config, and the
two SpecFlow attributes:

[BeforeFeature(new[]
{“WebServer”})] — Run the
code in the method adorned by this attribute before every feature that
has the @WebServer tag.

[AfterFeature(new[]{“WebServer”})] — Run the code in
the method adorned by this attribute after every feature that has the
@WebServer tag.

Quick points to note:

·
The methods have to be static to use these attributes.

·
Starting and stopping the web server is an expensive task in
terms of performance, so I do it before and after every feature — not every
scenario in a feature

If you don’t know how to use tags in SpecFlow, check this
out:

@WebServer

Feature:
Special Home Page Value Performs Special Redirect

This is actually an extract from my updated step file, so
now I should actually be able to run the integration tests. Assured that my
code will start the web server, host my application, and close down the server
when it is finished.

The Results Are In

Below is a screen shot from each step in the test — which I’ve
taken from the Internet Explorer window where all interactivity is automated by
Watin (based on the instructions in the step definitions).

Above: Watin enters the special input value in the
mysterious text box

Above: The application likes the secret value and
takes us to an invaluable web security resource (that should not be secret)

Conclusion

Happy days — we can now automate integration testing in a
development environment? Time for some beers and peanuts? Er…..

….using this approach the first time raised a few
questions — which I followed up by discussing with an ASP.NET team member and
other developers. The harsh words were, that ‘Cassini is Evil’ — quite
literally. For more information why this is check this Cassini
is Evil
. In short, there are big differences between Cassini and IIS which
may trip you up or let defects go undetected. If your client was the first to
find these, then….no, let’s stay positive.

So, in spite of Evil Cassini, I personally feel that it can
find errors during integration such as authentication rules. In an ASP.NET MVC
application, you need some web server simulation so that your filters will work
– and this is generally how most people apply authorisation rules.

I also feel that if you accept that this approach can be
used to find errors, but not confirm errors do not exist, (even if you test for
them) then you may gain small benefits. Not getting too confident based on
these specs passing is critical and will need to be employed by the whole
development team.

Personally, unless for some reason I am totally desperate (insert quip here), I will never be using this approach. Further,
I don’t even use Cassini as my local development server anymore. So what do I
do?…….

to be continued

--

--

Nick Tune
Strategy, Architecture, Continuous Delivery, and DDD

Principal Consultant @ Empathy Software and author of Architecture Modernization (Manning)