Crowbar is an application testing library for ASP.NET MVC 3 and 4. It is available as a NuGet.
Crowbar was inspired by the Nancy.Testing project from Nancy by Andreas HĂĄkansson, Steven Robbins and contributors. The Crowbar API is highly influenced by the Nancy.Testing API.
The source code of Crowbar is based on Steven Sanderson's MvcIntegrationTestFramework. The initial commit of Crowbar is Jon Canning's fork of the MvcIntegrationTestFramework.
Crowbar employs James Treworgy's jQuery port CsQuery for CSS selection and DOM manipulation. CsQuery is the only third-party dependency of Crowbar.
The MvcApplication
class (both generic and non-generic) is the heart of Crowbar and represents a proxy to the ASP.NET MVC project. Please note that creating the MvcApplication
instance is a time-consuming process and should preferably only be done once, e.g., in a test base class.
An instance of MvcApplication
can be created using the MvcApplication.Create()
facade methods.
public class MvcApplicationTests
{
private static readonly MvcApplication Application =
MvcApplication.Create("<ASP.NET MVC project>");
[Test]
public void Should_return_html()
{
Application.Execute(client =>
{
var response = client.Get("/route");
response.ShouldBeHtml();
});
}
}
public class MvcApplicationTests
{
protected static readonly MvcApplication<MvcHttpApplication, AppProxyContext> Application =
MvcApplication.Create<MvcHttpApplication, AppProxy, AppProxyContext>("<ASP.NET MVC project>");
[Test]
public void Should_return_html()
{
Application.Execute((client, context) =>
{
// Use any state in the context to bootstrap your test.
var response = client.Get("/route");
response.ShouldBeHtml();
});
}
}
By default, Crowbar, WebProjectPathProvider
, will attempt to locate the web project in the base directory of the current AppDomain
, if the web project is not found in the base directory Crowbar will move up one directory at a time until it reaches the root. If the web project is not found Crowbar will throw an exception. Crowbar, WebConfigPathProvider
, will look for Web.config in the base directory of the current AppDomain
. Should your web project and/or configuration file be located in a different location you will have to provide your own implementation of IPathProvider
and pass it to MvcApplicationFactory.Create()
.
public static class MvcApplicationFacade
{
public class CustomWebProjectPathProvider : IPathProvider
{
public string GetPhysicalPath()
{
// return the path to your web project.
}
}
public class CustomWebConfigPathProvider : IPathProvider
{
public string GetPhysicalPath()
{
// return the path to your Web.config.
}
}
public static MvcApplication Create()
{
return MvcApplicationFactory.Create<THttpApplication>(
new CustomWebProjectPathProvider(), new CustomWebConfigPathProvider());
}
}
In order to be able to pass state from the server (the MVC application) to the test case it is possible to define a user-defined context. The context must implement IDisposable
. The context will be created for each test and will be disposed after the test has been run.
public class AppProxyContext : IDisposable
{
// Add state from the server.
public void Dispose()
{
// Will be disposed after the test case has been run.
}
}
In order to initialize the user-defined context we need to defined our own proxy. This is done by deriving from MvcApplicationProxyBase<THttpApplication, TContext>
.
public class AppProxy : MvcApplicationProxyBase<MvcHttpApplication, UserDefinedContext>
{
protected override void OnApplicationStart(MvcHttpApplication application, string testBaseDirectory)
{
// This method will run once; after the application has been initialized by prior to any test.
}
protected override UserDefinedContext CreateContext(MvcHttpApplication application, string testBaseDirectory)
{
// This method will run before each test.
return new UserDefinedContext
{
// Set any state.
};
}
}
By default Crowbar tries to use the Web.config defined in the ASP.NET MVC project. To counter any problems with the configuration of the MVC project it is possible to use a custom configuration file. The name of the custom configuration file can be supplied as the second argument to MvcApplication.Create()
. The custom configuration file should be defined in the test project. The file must be copied to the output directory by setting Copy to Output Directory to either Copy always or Copy if newer.
Please note that the configuration file has not been replaced in the pre-initialization phase, i.e. when running any method specified by the PreApplicationStartMethodAttribute
from the .NET framework or from WebActivator.
private static readonly MvcApplication Application =
MvcApplication.Create("<ASP.NET MVC project>", "Web.Custom.config");
It is possible to define default HttpPayload
settings for each request by supplying a third argument to MvcApplication.Create()
. These settings will be applied to the HttpPayload
before the request-specific settings. See the section on HttpPayload for available options.
[Serializable]
public class HttpPayloadDefaults : IHttpPayloadDefaults
{
public void ApplyTo(HttpPayload payload)
{
payload.HttpsRequest();
}
}
private static readonly MvcApplication Application =
MvcApplication.Create("<ASP.NET MVC project>", "Web.Custom.config", new HttpPayloadDefaults());
An instance of the Client
class enables us to interact with the ASP.NET MVC application by performing requests.
var response = client.PerformRequest("GET", "/route");
The PerformRequest
method has an optional third argument which let us customize the request.
var response = client.PerformRequest("GET", "/route", payload => {
// Customize the request (HttpPayload object).
});
For convenience, the most common HTTP methods have their own methods: Delete
, Get
, Post
, Put
, which delegate to PerformRequest
. PerformRequest
returns an instance of ClientResponse
.
The Submit
and AjaxSubmit
extensions try to mimic the browser form submission behavior. This is done by extracting the form action, the form method, any form values for the supplied HTML and performing the request based on these values.
var response = client.Submit("<form action='/route' method='post'>...</form>", model);
Loads HTML from the server using GET and forwards it to either Submit
or AjaxSubmit
.
var continuation = client.Load("/path/to/form", payload => { // Modify the GET request. });
var response = continuation.Submit(model);
Renders HTML by reading it from disk and forwards it to either Submit
or AjaxSubmit
. The first argument can be either a string (the name of the view) or a PartialViewContext
object. By using the partial view context it is possible to state whether client validation and/or unobtrusive JavaScript is enabled and set the principal that should be used when rendering the view which is important when the form is rendered with an anti-forgery request token.
var continuation = client.Render("~/path/to/form.cshtml", model);
var response = continuation.Submit();
var context = new PartialViewContext("~/path/to/form.cshtml")
{
ClientValidationEnabled = true,
UnobtrusiveJavaScriptEnabled = true
};
context.SetFormsAuthPrincipal("username");
var response = client.Render(context, model).Submit();
The HttpPayload
class defines the HTTP payload of the HTTP request. As previously stated Client.PerformRequest()
provides us with the opportunity to customize the HTTP request.
To mark the request as an AJAX request call AjaxRequest()
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.AjaxRequest();
});
To set the body of the request use Body(string body, string contentType
). The second parameter is optional (the default content type is application/octet-stream).
To supply a cookie with the request use Cookie(HttpRequest cookie)
.
var response = client.PerformRequest("<method>", "<route>", payload => {
HttpCookie = ...
payload.Cookie(cookie);
});
To provide a form value with the request use FormValue(string key, string value)
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.FromValue("key", "value");
});
To provide an HTTP header with the request use Header(string name, string value)
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.Header("name", "value");
});
To mark the request as using the HTTP protocol (the default value) use HttpRequest()
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.HttpRequest();
});
To mark the request as using the HTTPS protocol use HttpsRequest()
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.HttpsRequest();
});
To provide a query string entry with the request use Query(string key, string value)
.
var response = client.PerformRequest("<method>", "<route>", payload => {
payload.Query("key", "value");
});
Crowbar provides several extension method to HttpPayload
.
Supplies a forms authentication cookie with the request.
Sets the content type to application/json (overridable) and provides a JSON object as the body of the request.
Sets the content type to application/xml (overridable) and provides an XML object as the body of the request.
The ClientResponse
class defines the properties of the HTTP response.
The Advanced
property provides access to various ASP.NET MVC context objects collected during the request.
Returns the content type of the response.
Provides access to the HTTP response headers.
Returns the string representation of the body of the response.
Returns the HTTP status code of the response.
Returns the HTTP status description of the response.
Returns a CsQuery object (similar to a jQuery object) which provides CSS selection and DOM manipulation functionality.
Returns a JSON object as a dynamic
object.
Returns an XML object as an XElement
object.
Crowbar provides several extension methods for easing the assertion of the correct behavior of the request.
Asserts that the HTTP status code is 'HTTP Status 200 OK' and that the content type is text/html. It provides an optional argument for performing additional assertions on the returned HTML document (CsQuery
). The CsQuery
object is also returned from the method.
response.ShouldBeHtml(document => {
var main = document["#main"];
Assert.That(main, Has.Length.EqualTo(1));
});
Asserts that the HTTP status code is 'HTTP Status 200 OK' and that the content type is application/json (overridable). It provides an optional argument for performing additional assertions on the returned JSON object (dynamic
). The dynamic
object is also returned from the method.
response.ShouldBeJson(json => {
Assert.That(json.value, Is.EqualTo("Crowbar"));
});
Asserts that the HTTP status code is 'HTTP Status 200 OK' and that the content type is application/xml (overridable). It provides an optional method for performing additional assertions on the returned XML object (XElement
). The XElement
object is also returned from the method.
response.ShouldBeXml(xml => {
Assert.That(xml.Value, Is.EqualTo("Crowbar"));
});
Asserts that the response has or has not a cookie with the specified name (and value).
response.ShouldHaveCookie("<name of cookie>");
response.ShouldHaveCookie("<name of cookie>", "<value of cookie");
response.ShouldNotHaveCookie("<name of cookie>");
Asserts that the HTTP status code is 'HTTP Status 301 MovedPermanently', that the header Location is defined and that the value of the header is equal to the expected location.
response.ShouldHavePermanentlyRedirectTo("/location");
Asserts that the HTTP status code is 'HTTP Status 302 Found', that the header Location is defined and that the value of the header is equal to the expected location.
response.ShouldHaveTemporarilyRedirectTo("/location");
If you're upgrading from v0.9.x to v0.10 please consult the migration guide.
The project contains several sample applications (which doubles as sanity test projects).
- Crowbar.Demo.Mvc is an MVC 4 application with no database server.
- Crowbar.Demo.Mvc.Async is an MVC 4 application which demonstrates the use of asynchronous action methods.
- Crowbar.Demo.Mvc.NHibernate is an MVC 4 application which uses NHibernate and a SQLite database server.
- Crowbar.Demo.Mvc.Raven is an MVC 4 application which uses a RavenDB database server.
- Crowbar.Demo.Mvc.WebApi is an MVC 4 application with a WebApi.
Crowbar is built using the ASP.NET MVC 3 assembly. If you're using ASP.NET MVC 4 then you should add a binding redirect in the root Web.config.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<configuration>
v0.10
Please consult migration guide v0.9.x to v0.10.
- Breaking change: The
ProxyBase
hierarchy has been re-written. The most notably change is that classes deriving fromProxyBase
take a generic argumentTHttpApplication
. - The
OnApplicationStart
method was added toProxyBase
. This method is called after the application has been started but prior to any test case. - Breaking change: the
BrowserContext
class has been renamedHttpPayload
. - Breaking change: the
Browser*
classes (Browser
,BrowserResponse
etc) have been renamedClient*
(Client
,ClientResponse
etc). - Breaking change: the parameter for the default HTTP payload settings is now an interface instead of a delegate. The use of a delegate forced a second initialization of the proxy.
- Proper support for asynchronous action method.
- Proper support for response headers.
- Breaking change: the
ClientResponse.HttpResponse
has been moved toClientResponse.Advanced.HttpResponse
. Furthermore, the dependency on the HTTP response object has been removed from the properties inClientResponse
, e.g. the content type and the status code is read from the response of the HTTP worker request instead of the HTTP response collected during the request.
v0.9.6
- Added
IPathProvider
,WebProjectPathProvider
andWebConfigPathProvider
. - The
MvcApplicationFactory
has been made public. In combination with the introduction ofIPathProvider
more advanced scenarios for locating the web project and Web.config are now supported.
v0.9.5
- It is possible to specify a form selector for
Browser.Submit()
. Previously the first form in the HTML chunk was chosen. - Breaking change:
CrowbarController
has been moved to the root namespace. This fixes a previous oversight when the class was made part of the public API. - Breaking change:
PartialViewContext
has been renamedCrowbarViewContext
. CrowbarViewContext
can now find both partial and non-partial views. Any view name starting with an underscore (_) is considered to be a partial view. It is possible to override this behavior inCrowbarViewContext.FindViewEngineResult()
should the default approach of finding the view not be to your satisfaction.- The settings
ClientValidationEnabled
andUnobtrusiveJavaScriptEnabled
inCrowbarViewContext
are now read from Web.config. Previously they were set totrue
by default. - Added
AreaName
andControllerName
toCrowbarViewContext
for better control over route data.
v0.9.4
ShouldBeHtml
,ShouldBeJson
andShouldBeXml
no longer assumes a HTTP Status 200 OK response.
v0.9.3
- Added the option of specifying a default
BrowserContext
(when creating theMvcApplication
) that will be applied to every request. - Additional assert helper,
ShouldNotHaveCookie
, for HTTP cookies.
v0.9.2
- Added
BrowserResponse.RawHttpRequest
which returns a raw string representation of the HTTP request (similar to Fiddler). If the server throws an exception the raw HTTP request will be included in the exception message for easier troubleshooting. - Assert helpers,
ShouldHaveCookie
, for HTTP cookies. - Added
Html
toBrowserLoadContinuation
for easier troubleshooting. - Added
Cookies
andHtml
toBrowserRenderContinuation<TContext>
for easier troubleshooting. - More descriptive error messages for
Browser.Submit()
.
v0.9.1
CrowbarController
,As
(new) andDelegateExtensions
(new) are now part of the public API.- Upgraded CsQuery to 1.3.4.
- Changes to the build script, enabled package restore.
v0.9
- Initial public release.