How do I test an ActionFilterAttribute with ASP.NET Web API? - asp.net

I'm building an ASP.NET Web API that uses a very simple action filter:
public class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
if (actionContext.ModelState.IsValid == false) {
var responseObject = new ApiResponse() {
Errors = actionContext.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, responseObject);
}
}
}
My action filter simply enforces that the ModelState is valid; if not, it returns a 400 with all the error messages so the client can do whatever they wish with the data.
Here is how I'm using the attribute in my controller:
[ValidateModelAttribute]
public HttpResponseMessage Db(DbModel model) {
return Request.CreateResponse(HttpStatusCode.OK);
}
I'm trying to test the attribute using Visual Studio's testing framework:
[TestMethod]
public void CaptureApiDatabase_IfRequiredFieldMissing_ReturnHttpBadRequest() {
var controller = new ConfigureController();
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
var validationAttribute = new ValidateModelAttribute();
validationAttribute.OnActionExecuting(controller.ActionContext);
Assert.IsTrue(controller.ActionContext.Response.StatusCode == HttpStatusCode.BadRequest);
}
This isn't working because I haven't provided the filter my data and as a result the ModelState is always valid. How do I build a context so I can test this filter attribute?

Related

FluentValidation: Mocking ValidationResult.IsValid

I'm using FluentValidation with WebAPI in DotNet core 2. I've written tests for the validator successfully, but now I'm trying to mock the validator for my controller. Controller as follows:
[Route("[controller]")]
public class SecurityController : Controller {
private readonly IValidator<AuthenticateRequest> _authenticateRequestValidator;
public SecurityController(IValidator<AuthenticateRequest> authenticateRequestValidator) {
_authenticateRequestValidator = authenticateRequestValidator;
}
[HttpPost]
[AllowAnonymous]
[Route("auth")]
public async Task<IActionResult> AuthenticateAsync([FromBody] AuthenticateRequest req) {
// Validate
var validator = await _authenticateRequestValidator.ValidateAsync(req);
if(!validator.IsValid) {
return BadRequest();
}
// ...snip
}
}
AuthenticateRequest looks like this:
public class AuthenticateRequest {
public string Username { get; set; }
public string Password { get; set; }
}
And the validator is as follows:
public class AuthenticateRequestValidator : AbstractValidator<AuthenticateRequest> {
/// <summary>
/// Provides a validator for <see cref="AuthenticateRequest" />
/// </summary>
public AuthenticateRequestValidator() {
RuleFor(x => x.Username)
.NotNull()
.NotEmpty()
.WithMessage("Username is required");
RuleFor(x => x.Password)
.NotNull()
.NotEmpty()
.WithMessage("Password is required");
}
}
It's being injected into the controller with dot net core's standard DI. Not posting code as it's not relevant to this issue, as its a testing issue.
I'm testing with xunit, Moq and AutoFixture. Here are two tests:
public class SecurityControllerTests {
private readonly IFixture Fixture = new Fixture().Customize(new AutoMoqCustomization {ConfigureMembers = true});
private readonly Mock<IValidator<AuthenticateRequest>> authenticateRequestValidatorMock;
public SecurityControllerTests() {
authenticateRequestValidatorMock = Mock.Get(Fixture.Create<IValidator<AuthenticateRequest>>());
}
[Fact]
public async Task Authenticate_ValidatesRequest() {
// Arrange
var request = Fixture.Create<AuthenticateRequest>();
authenticateRequestValidatorMock
.Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
.Returns(() => Fixture.Create<Task<ValidationResult>>())
.Verifiable();
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
// Act
await controller.AuthenticateAsync(request);
// Assert
authenticateRequestValidatorMock.Verify();
}
[Fact]
public async Task Authenticate_Returns400_WhenUsernameValidationFails() {
// Arrange
var request = Fixture.Create<AuthenticateRequest>();
var validationResultMock = new Mock<ValidationResult>();
validationResultMock
.SetupGet(x => x.IsValid)
.Returns(() => true);
authenticateRequestValidatorMock
.Setup(x => x.ValidateAsync(It.Is<AuthenticateRequest>(v => v == request), default(CancellationToken)))
.Returns(() => new Task<ValidationResult>(() => validationResultMock.Object));
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
// Act
var result = await controller.AuthenticateAsync(request);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
}
I need to mock ValidationResult so I can ignore the actual validator logic (which is tested elsewhere) and test the controller logic. There are many other dependencies injected, and much more code, but the pasted code is the crux of the problem and produces the same results when everything else is stripped out.
First test passes, second runs forever when it hits the var validator = await _authenticateRequestValidator.ValidateAsync(req); line in the controller.
Its worth noting that ValidationResult.IsValid is a virtual readonly property.
What is wrong with the second test?
Did you try Asp.Net Core - FluentValidation integration? By this way you dont need to pass Validator depedencies to constructor.
https://github.com/JeremySkinner/FluentValidation/wiki/i.-ASP.NET-Core-integration
FluentValidation fills ModelState in case of validation error and you use it like ;
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
For testing it you set ModelState of you Controllers Mock
var controller = new SecurityController(authenticationServiceMock.Object, tokenisationServiceMock.Object, authenticateRequestValidatorMock.Object);
controller.ModelState.AddModelError("test", "test");
// Act
IActionResult actionResult = await controller.AuthenticateAsync(request);
var badRequestObjectResult = actionResult as BadRequestObjectResult;
Assert.NotNull(badRequestObjectResult);
var serializableError = badRequestObjectResult.Value as SerializableError;
// Assert
Assert.NotNull(result);
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
var serializableError = assert.IsType<SerializableError>(badRequestResult.Value)
Assert.True(((string[])serializableError["test"])[0] == "test");
Leaving ModelState empty would be enough to ignore the actual validator logic i think.
Also FluentValidation have built-in testing api. You can test your validation logic separately.
https://github.com/JeremySkinner/FluentValidation/wiki/g.-Testing

Mixing Custom and Default Model Binding

I need to run some code to further databind some model after the default model binding is done. I don't want to completely replace the existing model binding.
This question explains how this is done in pre-CORE ASP.NET:
ASP.NET MVC - Mixing Custom and Default Model Binding
However that approach doesn't seem to work in ASP.NET Core because there is no DefaultModelBinder class any more.
What alternative can be used in ASP.NET Core?
You can leverage the ComplexTypeModelBinder to do the actual work, then inject your own logic after it is done.
For example (assuming your custom type is MyCustomType):
public class MyCustomType
{
public string Foo { get; set; }
}
public class MyCustomTypeModelBinder : IModelBinder
{
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public MyCustomTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
{
this._propertyBinders = propertyBinders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var complexTypeModelBinder = new ComplexTypeModelBinder(this._propertyBinders);
// call complexTypeModelBinder
await complexTypeModelBinder.BindModelAsync(bindingContext);
var modelBound = bindingContext.Model as MyCustomType;
// do your own magic here
modelBound.Foo = "custominjected";
}
}
public class MyCustomTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyCustomType))
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new MyCustomTypeModelBinder(propertyBinders);
}
return null;
}
}
Then register it:
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MyCustomTypeModelBinderProvider());
});

Testing razor pages app

I am writing a Razor Pages app for a University project, which I am required to test. I couldn't find many sources and examples online on testing Razor Pages and I'm trying to follow the examples on this link : https://learn.microsoft.com/en-us/aspnet/core/testing/razor-pages-testing?view=aspnetcore-2.1
My first problem is unit testing:
This is the test method I wrote, it's supposed to check that a value that is filled in the OnGet method on my model is receiving the correct value:
[Fact]
public void OnGet_ViewStores()
{
// Arrange
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var storesModel = new StoresModel()
{
PageContext = pageContext,
Url = new UrlHelper(actionContext)
};
#region snippet2
// Act
storesModel.OnGet();
#endregion
#region snippet3
// Assert
var actualStores = wsep1.Services.ViewAllStores(1);
Assert.Equal(storesModel.StoreDetails, actualStores);
#endregion
}
And this is the model which is being checked:
public class StoresModel : PageModel
{
public List<string> StoreDetails { get; set; }
public string Message { get; set; }
public int clientId;
public void OnGet()
{
clientId = (int)HttpContext.Session.GetInt32("clientId");
Message = "Your clientId id is " + clientId;
StoreDetails = wsep1.Services.ViewAllStores(clientId);
}
}
The problem is that the test throws an exception because I am using an HttpContext.Session which is not configures properly in the test. In my real project it is configured beforehand in Startup.cs in this method:
public void ConfigureServices(IServiceCollection services)
{
services.AddWebSocketManager();
services.AddMvc();
services.AddDistributedMemoryCache();
services.AddTransient<ShoppingHandler>();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(1000);
options.Cookie.HttpOnly = true;
});
}
But I can't seem to find a way to configure this in my test.
My second problem is with integration testing:
I am trying to run a very basic test with the Test Server, this is my test class:
public class IndexPageTest : IClassFixture<TestFixture<Client.Startup>>
{
private readonly HttpClient _client;
public IndexPageTest(TestFixture<Client.Startup> fixture)
{
_client = fixture.Client;
}
#region snippet1
[Fact]
public async Task Request_ReturnsSuccess()
{
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
}
#endregion
}
I hardly changed the TextFixture class that was included in the demo project in the link I gave at the beginning of the post, all I did was add my services to the configuration method (as I said before, I'm using a Session object and also WebSocketManager in my app).
_client.GetAsync("/") returns a status of "500 - internal server error" and I have no idea why and how to configure these tests to work.
Any ideas would be appreciated, Thanks.

How do you mock ServiceStack ISession using Moq and StructureMap?

I'm using ServiceStack / StructureMap / Moq. The service makes a call to Session, which is type ServiceStack.CacheAccess.ISession. For unit tests, I created a Mock object using Moq, and added it to the StructureMap configuration:
protected Mock<ISession> sessionMock = new Mock<ISession>();
ObjectFactory.Configure(
cfg =>
{
cfg.For<ISession>().Use(sessionMock.Object);
However, I was not surprised when the Session object was null -- I'm pretty sure I'm leaving out a step. What else do I need to do to fill my Session property with a mock object?
[EDIT] Here's a simple test scenario
Code to test. Simple request / service
[Route("getKey/{key}")]
public class MyRequest:IReturn<string>
{
public string Key { get; set; }
}
public class MyService:Service
{
public string Get(MyRequest request)
{
return (string) Session[request.Key];
}
}
The base test class and MockSession classes
// test base class
public abstract class MyTestBase : TestBase
{
protected IRestClient Client { get; set; }
protected override void Configure(Container container)
{
// this code is never reached under any of my scenarios below
container.Adapter = new StructureMapContainerAdapter();
ObjectFactory.Initialize(
cfg =>
{
cfg.For<ISession>().Singleton().Use<MockSession>();
});
}
}
public class MockSession : ISession
{
private Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public void Set<T>(string key, T value)
{
m_SessionStorage[key] = value;
}
public T Get<T>(string key)
{
return (T)m_SessionStorage[key];
}
public object this[string key]
{
get { return m_SessionStorage[key]; }
set { m_SessionStorage[key] = value; }
}
}
And tests. See comments for where I'm seeing the failure. I didn't really expect versions 1 & 2 to work, but hoped version 3 would.
[TestFixture]
public class When_getting_a_session_value:MyTestBase
{
[Test]
public void Test_version_1()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = new MyService(); // generally works fine, except for things like Session
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_2()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = ObjectFactory.GetInstance<MyService>();
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_3()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = CreateNewRestClient();
var result = client.Get(request); // throws NotImplementedException here
result.ShouldEqual("Test");
}
}
It looks like you're trying to create unit tests, but you're using an AppHost like you wound an Integration test. See this previous answer for differences between the two and docs on Testing.
You can mock the Session by registering an instance in Request.Items[Keywords.Session], e.g:
[Test]
public void Can_mock_IntegrationTest_Session_with_Request()
{
using var appHost = new BasicAppHost(typeof(MyService).Assembly).Init();
var req = new MockHttpRequest();
req.Items[Keywords.Session] = new AuthUserSession {
UserName = "Mocked"
};
using var service = HostContext.ResolveService<MyService>(req);
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}
Otherwise if you set AppHost.TestMode=true ServiceStack will return the IAuthSession that's registered in your IOC, e.g:
[Test]
public void Can_mock_UnitTest_Session_with_IOC()
{
using var appHost = new BasicAppHost
{
TestMode = true,
ConfigureContainer = container =>
{
container.Register<IAuthSession>(c => new AuthUserSession {
UserName = "Mocked",
});
}
}.Init();
var service = new MyService {
Request = new MockHttpRequest()
};
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}

Unit test controller - membership error

I want to create a Unit test for the following controller but it got fail in the Membership class:
public class AccountController:BaseController
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if(FormsService == null) { FormsService = new FormsAuthenticationService(); }
if(MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult LogOn()
{
return View("LogOn");
}
[HttpPost]
public ActionResult LogOnFromUser(LappLogonModel model, string returnUrl)
{
if(ModelState.IsValid)
{
string UserName = Membership.GetUserNameByEmail(model.Email);
if(MembershipService.ValidateUser(model.Email, model.Password))
{
FormsService.SignIn(UserName, true);
var service = new AuthenticateServicePack();
service.Authenticate(model.Email, model.Password);
return RedirectToAction("Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View("LogOn", model);
}
}
Unit test code:
[TestClass]
public class AccountControllerTest
{
[TestMethod]
public void LogOnPostTest()
{
var mockRequest = MockRepository.GenerateMock();
var target = new AccountController_Accessor();
target.Initialize(mockRequest);
var model = new LogonModel() { UserName = "test", Password = "1234" };
string returnUrl = string.Empty;
ActionResult expected = null;
ActionResult actual = target.LogOn(model, returnUrl);
if (actual == null)
Assert.Fail("should have redirected");
}
}
When I googled, I got the following code but I don't know how to pass the membership to the accountcontroller
var httpContext = MockRepository.GenerateMock();
var httpRequest = MockRepository.GenerateMock();
httpContext.Stub(x => x.Request).Return(httpRequest);
httpRequest.Stub(x => x.HttpMethod).Return("POST");
//create a mock MembershipProvider & set expectation
var membershipProvider = MockRepository.GenerateMock();
membershipProvider.Expect(x => x.ValidateUser(username, password)).Return(false);
//create a stub IFormsAuthentication
var formsAuth = MockRepository.GenerateStub();
/*But what to do here???{...............
........................................
........................................}*/
controller.LogOnFromUser(model, returnUrl);
Please help me to get this code working.
It appears as though you are using concrete instances of the IMembershipServive and IFormsAuthenticationService because you are using the Accessor to initialize them. When you use concrete classes you are not really testing this class in isolation, which explains the problems you are seeing.
What you really want to do is test the logic of the controller, not the functionalities of the other services.
Fortunately, it's an easy fix because the MembershipService and FormsService are public members of the controller and can be replaced with mock implementations.
// moq syntax:
var membershipMock = new Mock<IMembershipService>();
var formsMock = new Mock<IFormsAuthenticationService>();
target.FormsService = formsMock.Object;
target.MembershipService = membershipService.Object;
Now you can test several scenarios for your controller:
What happens when the MembershipService doesn't find the user?
The password is invalid?
The user and password is is valid?
Note that your AuthenticationServicePack is also going to cause problems if it has additional services or dependencies. You might want to consider moving that to a property of the controller or if it needs to be a single instance per authentication, consider using a factory or other service to encapsuate this logic.

Resources