Skip to content

Instantly share code, notes, and snippets.

@RhysC
Last active January 2, 2016 16:28
Show Gist options
  • Save RhysC/8329873 to your computer and use it in GitHub Desktop.
Save RhysC/8329873 to your computer and use it in GitHub Desktop.
MVC test helpers (.net 4.5) using Log4net, NSubstitute and xUnit. Intend for : - checking attributation of the controller and actions. - asserting ActionResult values in a fluent manner - reducing set up noise for underling type that help form a testable controller
Example usages:
public class MyControllerFixture : IUseFixture<ControllerFixtureInit>
{
private readonly MyController _sut;
private ControllerTestContext _controllerTestContext;
private readonly IMyDependency _dependency;
public MyControllerFixture()
{
_dependency = Substitute.For<IMyDependency>();
_sut = new MyController(_dependency);
}
public void SetFixture(ControllerFixtureInit data)
{
_controllerTestContext = data.Init(_sut);
}
[Fact]
public void LogOffIsAPostVerb()
{
_sut.Action(c => c.LogOff())
.Has<HttpPostAttribute>();
}
[Fact]
public void LoginGetRedirectsToRootIfUserIsAlreadyAuthenticated()
{
_controllerTestContext.SetCurrentUser(AuthenticationAndAuthorization.GetFakePrinciple(isLoggedIn: true));
_sut.Login(_returnUrl)
.As<RedirectResult>()
.HasRedirectUrl("/");
}
[Fact]
public void LoginWhenUserAlreadyAuthenticatedLogsWarning()
{
_controllerTestContext.SetCurrentUser(AuthenticationAndAuthorization.GetFakePrinciple(isLoggedIn: true));
_sut.Login(_returnUrl);
Assert.True(_controllerTestContext.GetLogEvents().Any(le => le.Level == Level.Warn), "Expected warning messages in the logs");
}
[Fact]
public void LoginPostReturnsViewWithPassedInReturnUrlAndModelStateErrorIfModelStateIsInvalid()
{
_sut.ModelState.AddModelError("key", "something was wrong");
var loginModel = new LoginModel();
_sut.Login(loginModel, _returnUrl)
.As<ViewResult>()
.HasViewName("")
.HasViewBagProperty("ReturnUrl", _returnUrl)
.HasViewModel(loginModel)
.HasModelStateErrorMessage("", "The user name or password provided is incorrect.")
.HasModelStateErrorMessage("key", "something was wrong");
}
}
using System.Linq;
using System.Security.Principal;
using NSubstitute;
namespace MvcTestHelper
{
public class AuthenticationAndAuthorization
{
public static IPrincipal GetFakePrinciple(string[] roles)
{
return GetFakePrinciple(true, roles);
}
public static IPrincipal GetFakePrinciple(bool isLoggedIn, string[] roles = null)
{
if (roles == null) roles = new string[] { };
var mock = Substitute.For<IPrincipal>();
var fakeIdentity = GetFakeIdentity(isLoggedIn);
mock.Identity.Returns(fakeIdentity);
mock.IsInRole(Arg.Is<string>(x => roles.Contains(x)))
.Returns(true);
return mock;
}
public static IIdentity GetFakeIdentity(bool isLoggedIn)
{
var mock = Substitute.For<IIdentity>();
mock.AuthenticationType.Returns(isLoggedIn ? "Mock Identity" : null);
mock.IsAuthenticated.Returns(isLoggedIn);
mock.Name.Returns(isLoggedIn ? "testuser" : null);
return mock;
}
}
}
using System.Web.Mvc;
namespace MvcTestHelper
{
/// <summary>
/// To be used with xUnits IUseFixture
/// </summary>
public class ControllerFixtureInit
{
public ControllerTestContext Init(Controller sut, string currentUrl = "http://localhost:123")
{
var ctx = new ControllerTestContext(sut);
ctx.SetCurrentUrl(currentUrl);
return ctx;
}
}
}
using System;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.SessionState;
using NSubstitute;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
namespace MvcTestHelper
{
public class ControllerTestContext
{
private readonly HttpRequestBase _currentRequest;
private readonly HttpContextBase _currentHttpContext;
private readonly RouteData _routeData;
private readonly Controller _sut;
private readonly MemoryAppender _memoryAppender;
public ControllerTestContext(Controller sut)
{
_sut = sut;
_currentHttpContext = Substitute.For<HttpContextBase>();
_currentRequest = Substitute.For<HttpRequestBase>();
_currentHttpContext.Request.Returns(_currentRequest);
_routeData = new RouteData();
_sut.ControllerContext = new ControllerContext(_currentHttpContext, _routeData, _sut);
_memoryAppender = new MemoryAppender();
BasicConfigurator.Configure(_memoryAppender);
}
public void SetCurrentUrl(string url)
{
_currentRequest.Url.Returns(new Uri(url));
var requestContext = new RequestContext(_currentHttpContext, _routeData);
_sut.Url = new UrlHelper(requestContext);
}
public void SetCurrentUser(IPrincipal principal)
{
_currentHttpContext.User.Returns(principal);
}
public LoggingEvent[] GetLogEvents()
{
return _memoryAppender.GetEvents();
}
public HttpSessionStateBase CreateSession()
{
var session = new FakeHttpSessionState(new SessionStateItemCollection());
_currentHttpContext.Session.Returns(session);
return session;
}
}
}
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using Xunit;
namespace MvcTestHelper
{
public static class ControllerTestExtensions
{
public static TAttribute Has<TAttribute>(this Controller controller) where TAttribute : Attribute
{
var attribute = controller.GetType().GetCustomAttributes<TAttribute>().Single();
Assert.NotNull(attribute);
return attribute;
}
public static TAttribute With<TAttribute>(this TAttribute attribute, Func<TAttribute, bool> predicate) where TAttribute : Attribute
{
Assert.True(predicate(attribute));
return attribute;
}
public static MethodInfo Action<TController>(this TController controller, Expression<Func<TController, ActionResult>> expression)
{
var methodCall = expression.Body as MethodCallExpression;
if (methodCall == null) throw new InvalidOperationException("Expression body is expected to be a MethodCallExpression. This is design for Action on Controllers which must be methods.");
return methodCall.Method;
}
public static T Has<T>(this MethodInfo methodInfo) where T : Attribute
{
var att = methodInfo.GetCustomAttributes(typeof(T), true).Single();
Assert.NotNull(att);
return att as T;
}
public static TResult As<TResult>(this ActionResult actionResult) where TResult : ActionResult
{
Assert.IsAssignableFrom<TResult>(actionResult);
return actionResult as TResult;
}
public static TResult HasViewName<TResult>(this TResult viewResult, string viewName) where TResult : ViewResult
{
Assert.Equal(viewName, viewResult.ViewName);
return viewResult;
}
public static TResult HasViewModel<TResult>(this TResult viewResult, object viewModel) where TResult : ViewResult
{
Assert.Equal(viewModel, viewResult.Model);
return viewResult;
}
public static TResult HasViewBagProperty<TResult>(this TResult viewResult, string viewBagPropertyName, object expectedPropertyValue) where TResult : ViewResult
{
return viewResult.HasViewDataProperty(viewBagPropertyName, expectedPropertyValue);//Viewdata is just the string dict of the view bag
}
public static TResult HasViewDataProperty<TResult>(this TResult viewResult, string viewBagPropertyName, object expectedPropertyValue) where TResult : ViewResult
{
Assert.Equal(expectedPropertyValue, viewResult.ViewData[viewBagPropertyName]);
return viewResult;
}
public static TResult HasModelStateErrorMessage<TResult>(this TResult viewResult, string modelStateErrorKey, object expectedErrorMessage) where TResult : ViewResult
{
Assert.Equal(expectedErrorMessage, viewResult.ViewData.ModelState.Single(msd => msd.Key == modelStateErrorKey).Value.Errors.Single().ErrorMessage);
return viewResult;
}
public static TResult HasRedirectUrl<TResult>(this TResult redirectResult, string expectedUrl) where TResult : RedirectResult
{
Assert.Equal(expectedUrl, redirectResult.Url);
return redirectResult;
}
public static TResult HasActionName<TResult>(this TResult redirectResult, string expectedName) where TResult : RedirectToRouteResult
{
Assert.Equal(expectedName, redirectResult.RouteValues["action"]);
return redirectResult;
}
public static TResult HasRouteValues<TResult>(this TResult redirectResult, object expectedValues) where TResult : RedirectToRouteResult
{
//Assume this is an Anon object
var properties = expectedValues.GetType().GetProperties();
foreach (var propertyInfo in properties)
{
var expectedValue = propertyInfo.GetValue(expectedValues, null);
Assert.Equal(expectedValue, redirectResult.RouteValues[propertyInfo.Name]);
}
return redirectResult;
}
}
}
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.SessionState;
namespace MvcTestHelper
{
/// <summary>
/// http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context
/// </summary>
internal class FakeHttpSessionState : HttpSessionStateBase
{
private readonly SessionStateItemCollection _sessionItems;
public FakeHttpSessionState(SessionStateItemCollection sessionItems)
{
_sessionItems = sessionItems;
}
public override void Add(string name, object value)
{
_sessionItems[name] = value;
}
public override int Count
{
get { return _sessionItems.Count; }
}
public override IEnumerator GetEnumerator()
{
return _sessionItems.GetEnumerator();
}
public override NameObjectCollectionBase.KeysCollection Keys { get { return _sessionItems.Keys; } }
public override object this[string name]
{
get { return _sessionItems[name]; }
set { _sessionItems[name] = value; }
}
public override object this[int index]
{
get { return _sessionItems[index]; }
set { _sessionItems[index] = value; }
}
public override void Remove(string name)
{
_sessionItems.Remove(name);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net45" />
<package id="NSubstitute" version="1.7.0.0" targetFramework="net40" />
<package id="xunit" version="1.9.2" targetFramework="net40" />
</packages>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment