I have the following attribute:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
I have this generic extension method for Determining whether the attribute is applied to a method or not
public static bool ActionHasFilter(this ApiController controller, string action, Type filter)
{
var controllerType = controller.GetType();
var method = controllerType.GetMethod(action);
object[] filters = method.GetCustomAttributes(filter, true);
return filters.Any(x=>x.GetType() == filter);
}
My problem is how do i go about testing whether the attribute actually works or not, without testing the controller action?
Let's say i have the following Entity
public class UserViewModel
{
[Required]
public string Name {get; set;}
[Required]
[EmailAddress]
public string Email {get;set;
}
How would i go about mocking the context and checking whether the model is valid?
I'm using Nunit and Moq.
Spock's solution is on the right track, but is a bit too invasive on the code, because it makes the ValidateModelAttribute
class dependent on TestableHttpActionContext
. My implementation uses a public property that will be used to "inject" the Request
object for the Unit Tests, while the implementation as attribute continues to use the Request
object from the ActionContext
:
public class ValidateModelAttribute : ActionFilterAttribute
{
public HttpRequestMessage TestRequestMessage { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
PerformValidation(actionContext, TestRequestMessage ?? actionContext.Request);
}
private void PerformValidation(HttpActionContext actionContext, HttpRequestMessage request)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Unit tests:
[Test]
public void OnActionExecuting_ValidModel_ResponseIsNotSet()
{
var actionContext = new HttpActionContext();
actionContext.ModelState.Clear();
var attribute = new ValidateModelAttribute { TestRequestMessage = new HttpRequestMessage() };
attribute.OnActionExecuting(actionContext);
Assert.IsNull(actionContext.Response);
}
[Test]
public void OnActionExecuting_InvalidModel_ResponseIsSetToBadRequest()
{
var actionContext = new HttpActionContext();
actionContext.ModelState.AddModelError("key", "error");
var attribute = new ValidateModelAttribute() { TestRequestMessage = new HttpRequestMessage() };
attribute.OnActionExecuting(actionContext);
Assert.AreEqual(HttpStatusCode.BadRequest, actionContext.Response.StatusCode);
}
Please note that I'm not using an actual model to validate the ModelState
, because that's out of scope for the Unit Tests: We want to test the ModelState
's outcome, not the ModelState
itself. ;-)
You would do this as below.
Couple of things you need to consider.
ModelState does not need be stubbed out, you can just Add Model error which causes actionContext.ModelState.IsValid to false.
contextStub.Object.ModelState.AddModelError("key", "error");
Also you don't required UserViewModel
You cannot use the Moq to setup actionContext.Request as the Request property is non-virtual. Also it has a getter only you cannot provide your own HttpRequestMessage either.
To get around with this you need to abstract the HttpActionContext and create Virtual property so Moq can provide a setup. A technique can be called Extract Override
public class TestableHttpActionContext : HttpActionContext
{
public virtual new HttpRequestMessage Request { get; set; }
}
Now you action method can be modified as below.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
TestableActionExecuting((TestableHttpActionContext)actionContext);
}
private void TestableActionExecuting(TestableHttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Now you can Unit Test the OnActionExecuting as below.
[TestFixture]
public class UnitTest1
{
[Test]
public void OnActionExecuting_ErrorResponse_ExpectBadRequest()
{
var sut = new ValidateModelAttribute();
var contextStub = new Mock<TestableHttpActionContext>();
contextStub.Object.ModelState.AddModelError("key", "error");
contextStub.Setup(x => x.Request).Returns(new HttpRequestMessage());
sut.OnActionExecuting(contextStub.Object);
Assert.AreEqual("BadRequest",
contextStub.Object.Response.StatusCode.ToString());
}
}