I am currently looking into (ideally headless) integration testing frameworks for asp.net mvc 5 (potentially with webapi in the same project). I am aware of these 2:
Are there any others? I am especially interested in any frameworks that work well with specflow.
I successfully integrated SpecsFor.Mvc with SpecFlow today. It's pretty cool.
Here's a set of classes that should get you started with integrating SpecsFor.Mvc with SpecFlow. Of course, these could be better abstracted and extended upon; but at a bare minimum, this is all you need:
namespace SpecsForMvc.SpecFlowIntegration
{
using Microsoft.VisualStudio.TestingTools.UnitTesting;
using SpecsFor.Mvc;
using TechTalk.SpecFlow;
[Binding]
public class SpecsForMvcSpecFlowHooks
{
private static SpecsForIntegrationHost integrationHost;
/// <summary>
/// <p>
/// This hook runs at the end of the entire test run.
/// It's analogous to an MSTest method decorated with the
/// <see cref="Microsoft.VisualStudio.TestingTools.UnitTesting.AssemblyCleanupAttribute" />
/// attribute.
/// </p>
/// <p>
/// NOTE: Not all test runners have the notion of a test run cleanup.
/// If using MSTest, this probably gets run in a method decorated with
/// the attribute mentioned above. For other test runners, this method
/// may not execute until the test DLL is unloaded. YMMV.
/// </p>
/// </summary>
[AfterTestRun]
public void CleanUpTestRun()
{
integrationHost.Shutdown();
}
/// <summary>
/// <p>
/// This hook runs at the beginning of an entire test run.
/// It's equivalent to an MSTest method decorated with the
/// <see cref="Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyInitializeAttribute />
/// attribute.
/// </p>
/// <p>
/// NOTE: Not all test runners have a notion of an assembly
/// initializer or test run initializer, YMMV.
/// </p>
/// </summary>
[BeforeTestRun]
public static void InitializeTestRun()
{
var config = new SpecsForMvcConfig();
config.UseIISExpress()
.With(Project.Named("Your Project Name Here"))
.ApplyWebConfigTransformForConfig("Debug");
config.BuildRoutesUsing(r => RouteConfig.RegisterRoutes(r));
// If you want to be authenticated for each request,
// implement IHandleAuthentication
config.AuthenticateBeforeEachTestUsing<SampleAuthenticator>();
// I originally tried to use Chrome, but the Selenium
// Chrome WebDriver, but it must be out of date because
// Chrome gave me an error and the tests didn't run (NOTE:
// I used the latest Selenium NuGet package as of
// 23-08-2014). However, Firefox worked fine, so I used that.
config.UseBrowser(BrowserDriver.Firefox);
integrationHost = new SpecsForMvcIntegrationHost(config);
integrationHost.Start();
}
/// <summary>
/// This hook runs once before any of the SpecFlow feature's
/// scenarios are run and stores a <see cref="SpecsFor.Mvc.MvcWebApp />
/// instance in the <see cref="TechTalk.SpecFlow.FeatureContext" />
/// for the feature.
/// </summary>
[BeforeFeature]
public static void CreateFeatureMvcWebApp()
{
MvcWebApp theApp;
if (!FeatureContext.Current.TryGetValue<MvcWebApp>(out theApp))
FeatureContext.Current.Set<MvcWebApp>(new MvcWebApp());
}
}
public class SpecsForMvcStepDefinitionBase
{
/// <summary>
/// Gets the instance of the <see cref="SpecsFor.Mvc.MvcWebApp" />
/// object stored in the <see cref="TechTalk.SpecFlow.FeatureContext" />
/// for the current feature.
/// </summary>
public static MvcWebApp WebApp
{
get { return FeatureContext.Current.Get<MvcWebApp>(); }
}
}
}
Then, let's say you have a SpecFlow feature file as per the below (this is a partial file):
Feature: Login
As a user of the website
I want to be able to log on the the site
in order to use the features available to site members.
# For the site I'm currently working with, even though it's MVC, it's more
# of a WebAPI before there was WebAPI--so the controllers accept JSON and return
# JsonResult objects--so that's what you're going to see.
Scenario: Using a valid username and password logs me on to the site
Given the valid username '[email protected]'
And the password 'my_super_secure_password'
When the username and password are submitted to the login form
Then the website will return a result
And it will contain an authentication token
And it will not contain any exception record.
And now the steps for the above scenario:
using Newtonsoft.Json;
using OpenQA.Selenium;
using MyWebSite.Controllers;
using Should;
using TechTalk.SpecFlow;
[Binding]
public class LoginSteps : SpecsForMvcStepDefinitionBase
{
// The base class gets me the WebApp property that allows easy
// access to the SpecsFor.Mvc.MvcWebApp object that drives a web browser
// via Selenium.
private string _username;
private string _password;
private string _portalSessionId;
private ServiceResponse<LoginSummary> _loginResponse;
[Given(@"the valid username '(.*)'")]
[Given(@"the invalid username '(.*)'")]
public void GivenAUsername(string username)
{
_username = username;
}
[Given(@"the valid password '(.*)'")]
[Given(@"the invalid password '(.*)'")]
public void GivenAPassword(string password)
{
_password = password;
}
[When(@"the username and password are submitted to the " +
@"LoginUser action method of the UserController")]
public void WhenTheUsernameAndPasswordAreSubmitted()
{
WebApp.NavigateTo<UserController>(
c => c.LoginUser(_username, _password)
);
}
[Then(@"the UserController will reply with a LoginSummary")]
public void ThenTheUserControllerWillReplyWithALoginSummary()
{
_loginResponse =
JsonConvert.DeserializeObject<ServiceResponse<LoginSummary>>(
WebApp.Browser.FindElement(By.TagName("pre")).Text
);
}
[Then(@"it will contain an authentication token")]
public void ThenItWillContainAnAuthenticationToken()
{
_loginSummary.Results.Count.ShouldBeGreaterThan(0);
_loginSummary.Results[0].AuthenticationToken.ShouldNotBeEmpty();
}
[Then(@"it will not contain an exception record")]
public void THenItWillNotContainAnExceptionRecord()
{
_loginSummary.Exception.ShouldBeNull();
}
}
Pretty cool.
About the methods decorated with BeforeTestRun
and AfterTestRun
, I found the information mentioned in the code comments at the following blog post: Advanced SpecFlow: Using Hooks to Run Additional Automation Code.
Of course, you may still want to construct classes that follow the Page Object pattern if you'll be testing presentation layout. As I stated in my code comments, the particular app I'm writing integration tests for pre-dates WebAPI but function like WebAPI but using ASP.Net MVC. We just haven't officially ported it to WebAPI yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With