-
-
Save cc11112/2965b459d4084d35461e 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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