How to unit test an ActionResult that returns a ContentResult? - asp.net-mvc

I want to unit test the following ASP.NET MVC controller Index action. What do I replace the actual parameter in the assert below (stubbed with ?).
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class StatusController : Controller
{
public ActionResult Index()
{
return Content("Hello World!");
}
}
}
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index();
// Assert
Assert.AreEqual( "Hello World!.", ? );
}

use the "as" operator to make a nullable cast. Then simply check for a null result
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index() as ContentResult;
// Assert
Assert.NotNull(result);
Assert.AreEqual( "Hello World!.", result.Content);
}

I like creating assertion helpers for this sort of thing. For instance, you might do something like:
public static class AssertActionResult {
public static void IsContentResult(ActionResult result, string contentToMatch) {
var contentResult = result as ContentResult;
Assert.NotNull(contentResult);
Assert.AreEqual(contentToMatch, contentResult.Content);
}
}
You'd then call this like:
[TestMethod]
public void TestMethod1()
{
var controller = CreateStatusController();
var result = controller.Index();
AssertActionResult.IsContentResult(result, "Hello World!");
}
I think this makes the tests so much easier to read and write.

You cant test that the result is not null, that you receive a ContentResult and compare the values:
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index();
// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom(typeof(ContentResult), result);
Assert.AreEqual( "Hello World!.", result.Content);
}
I apoligize if the Nunit asserts aren't welformed, but look at it as pseudo-code :)

Related

MVC 6 How can I include a BaseRepository in my controller class

I am using an ORM to connect to the database it is called dapper. The issue with it is that it's database calls are synchronous and I recently found a way to make it asynchronous by following this short tutorial http://www.joesauve.com/async-dapper-and-async-sql-connection-management/ . My question is how can I bring this BaseRepository into my Controller class ? This is the code on that website and it's the same one I have
BaseRepository- by the way there is no issue in this code
public abstract class BaseRepository
{
private readonly string _ConnectionString;
protected BaseRepository(string connectionString)
{
_ConnectionString = connectionString;
}
protected async Task<T> WithConnection<T>(Func<IDbConnection, Task<T>> getData)
{
try {
using (var connection = new SqlConnection(_ConnectionString)) {
await connection.OpenAsync(); // Asynchronously open a connection to the database
return await getData(connection); // Asynchronously execute getData, which has been passed in as a Func<IDBConnection, Task<T>>
}
}
catch (TimeoutException ex) {
throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex);
}
catch (SqlException ex) {
throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex);
}
}
}
and now he brings it in like this
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}
The part I am having a problem with is this public class PersonRepository : BaseRepository because Asp.Net Controllers start with public class HomeController: Controller , I need access to the WithConnection method to get this working. My controller looks like this
public class HomeController : Controller
{
public class ConnectionRepository : BaseRepository
{
public ConnectionRepository(string connectionString) : base(connectionString) { }
}
public async Task<ActionResult> topfive()
{
// I get Error on WithConnection as it can't see the BaseRepository
return await WithConnection(async c =>
{
var topfive = await c.QueryAsync<Streams>("select * from streams ").ToList();
return View(topfive);
});
}
}
I obviously can not cover my ActionResult method with the BaseRepository because it gives all types of errors any suggestions ?
Why are you using inheritance instead of composition? What about something like:
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}
public class ConnectionRepository : BaseRepository
{
public ConnectionRepository(string connectionString) : base(connectionString) { }
}
public async Task<List<TopFileClass>> topfive()
{
// I get Error on WithConnection as it can't see the BaseRepository
return await WithConnection(async c =>
{
var topfive = await c.QueryAsync<Streams>("select * from streams ").ToList();
return topfive;
});
}
public class HomeController : Controller
{
private readonly PersonRepository _repo;
public HomeController(PersonRepository repo)
{
_repo = repo;
}
public async Task<ActionResult> TopFive()
{
var top5 = await _repo.topfive();
return View(top5);
}
}
If you are not familiar how to make the repository automatically get injected into the constructor, read up on dependency injection in MVC 6.
you have to intehirt the "BaseRepository" from "Controller". i think that will work for you. then just go with below code:
public abstract class BaseRepository : Controller
{
// do you work
}
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}

unit test fails in edit _get action

i wrote a unit test for edit_get action
My controller action is
public class GroupController : Controller
{
private readonly IGroupService groupService;
public GroupController(IGroupService groupService)
{
this.groupService = groupService;
}
public ActionResult EditGroup(int id)
{
var group = groupService.GetGroup(id);
CreateGroupFormModel editGroup = Mapper.Map<Group, CreateGroupFormModel>(group);
if (group == null)
{
return HttpNotFound();
}
return View("_EditGroup", editGroup);
}
the controller actionis working fine.
but when i am writing my unit test it get failed
my test is
[Test]
public void Edit_Get_ReturnsView()
{
//Arrange
CreateGroupFormModel group = new CreateGroupFormModel()
{
GroupId = 2,
GroupName = "Test",
Description = "test" };
GroupController controller = new GroupController();
var fake = groupService.GetGroup(2);
groupRepository.Setup(x => x.GetById(2)).Returns(fake);
Mapper.CreateMap<CreateGroupFormModel, Group>().ForAllMembers(opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
ViewResult actual = controller.EditGroup(2) as ViewResult;
Assert.IsNotNull(actual, "View Result is null");
}
can any one help me. the test fails as
Expected Not Null
actual Null
In your controller action you are calling var group = groupService.GetGroup(id); and it is not very clear where this groupService is coming from. In your unit test you must mock it. For this to be possible your GroupController must take this dependency as constructor injection.
Also in your unit test you seem to have declared some group variable which is never used.
For example:
public class GroupController: Controller
{
private readonly IGroupService groupService;
public GroupController(IGroupService groupService)
{
this.groupService = groupService;
}
public ActionResult EditGroup(int id)
{
var group = this.groupService.GetGroup(id);
CreateGroupFormModel editGroup = Mapper.Map<Group, CreateGroupFormModel>(group);
if (group == null)
{
return HttpNotFound();
}
return View("_EditGroup", editGroup);
}
}
and now in your unit test you could mock this group service and provide expectations on the result of the GetGroup method:
[Test]
public void Edit_Get_ReturnsView()
{
// arrange
var group = new CreateGroupFormModel
{
GroupId = 2,
GroupName ="Test",
Description ="test"
};
var groupServiceMock = new Mock<IGroupService>();
groupServiceMock.Setup(x => x.GetGroup(group.GroupId)).Returns(group);
var sut = new GroupController(groupServiceMock.Object);
Mapper.CreateMap<CreateGroupFormModel, Group>().ForAllMembers(opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
// act
var actual = sut.EditGroup(group.GroupId) as ViewResult;
// assert
Assert.IsNotNull(actual);
Assert.IsInstanceOfType(typeof(ViewResult), actual);
}

How to get result from ControllerContext

My controller action returns a custom ActionResult that executes either a success or failure result depending on some validation logic. This happens within ExecuteResult.
My question is, how can I check the result?
Here's the test so far:
[TestFixture]
public class FormActionResultTests
{
TestController controller;
[SetUp]
public void SetUp()
{
ObjectFactory.Initialize(cfg =>
{
cfg.For<IFormHandler<TestModel>>().Use<TestModelHandler>();
});
controller = new TestControllerBuilder().CreateController<TestController>();
}
[Test]
public void Valid_input_returns_success_result()
{
var result = controller.Test(new TestModel { IsValid = true, IsValid2 = true })
.AssertResultIs<FormActionResult<TestModel>>();
var context = controller.ControllerContext;
result.ExecuteResult(context);
// how to verify result?
}
}
public class TestController : Controller
{
public ActionResult Test(TestModel model) {
return new FormActionResult<TestModel>(model, this.Content("Success"), View(model));
}
}
public class TestModel {
public bool IsValid { get; set; }
public bool IsValid2 { get; set; }
}
public class TestModelHandler : IFormHandler<TestModel>
{
public void Handle(TestModel form, IValidationDictionary validationDictionary)
{
}
}
Update
Here's what worked for me in the end (using NSubstitute):
[Test]
public void Valid_input_returns_success_result()
{
var result = new FormActionResult<TestModel>(new TestModel { IsValid = true, IsValid2 = true },
new ContentResult { Content = "Success" }, new ContentResult { Content = "Failed" });
var sb = new StringBuilder();
var response = Substitute.For<HttpResponseBase>();
response.When(x => x.Write(Arg.Any<string>())).Do(ctx => sb.Append(ctx.Arg<string>()));
var httpContext = Substitute.For<HttpContextBase>();
httpContext.Response.Returns(response);
var controllerContext = new ControllerContext(httpContext, new RouteData(), new TestController());
result.ExecuteResult(controllerContext);
sb.ToString().ShouldEqual("Success");
}
Controller should be tested that they return correct ActionResult in your case, and the Success or Failure of ActionResult should be tested by ActionResultTest and it has nothing to do with controller. Unit test means single unit test - but you test both controller and ActionResult in the same test, that is incorrect. To test ActionResult, first imagine that generally all that ActionResult does, is writing result to HttpResponse. Let's rewrite your code to use Moq to supply StringWriter for ControllerContext.HttpContext.HttpResponse.Output
[Test]
public void Valid_input_returns_success_result()
{
var result = controller.Test(new TestModel { IsValid = true, IsValid2 = true })
.AssertResultIs<FormActionResult<TestModel>>();
var context = controller.ControllerContext;
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
StringWriter actionResultOutput = new StringWriter();
mockHttpContext.Setup(x => x.Response.Output).Returns(actionResultOutput);
context.HttpContext = mockHttpContext.Object;
result.ExecuteResult(context);
// how to verify result? Examine actionResultOutput
}
All is left to examine actionResultOutput. For example, if your action result is designed to return string "Success" when validation is ok and "Error" when validation failed, compare these strings to actionResultOutput.ToString(). If your result view's generated html is more complex, you can use HtmlAgilityPack to examine output more deeply
You should write a simple unit test of the Test-action, asserting on the returned action result. You shouldn't depend on the MVC framework in your test. Simply create a new instance of TestController and call the Test method.

How to write a test for accounts controller for forms authenticate

Trying to figure out how to adequately test my accounts controller. I am having problem testing the successful logon scenario.
Issue 1) Am I missing any other tests.(I am testing the model validation attributes separately)
Issue 2) Put_ReturnsOverviewRedirectToRouteResultIfLogonSuccessAndNoReturnUrlGiven() and Put_ReturnsRedirectResultIfLogonSuccessAndReturnUrlGiven() test are not passing. I have narrowed it down to the line where i am calling _membership.validateuser(). Even though during my mock setup of the service i am stating that i want to return true whenever validateuser is called, the method call returns false.
Here is what I have gotten so far
AccountController.cs
[HandleError]
public class AccountController : Controller
{
private IMembershipService _membershipService;
public AccountController()
: this(null)
{
}
public AccountController(IMembershipService membershipService)
{
_membershipService = membershipService ?? new AccountMembershipService();
}
[HttpGet]
public ActionResult LogOn()
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (_membershipService.ValidateUser(model.UserName,model.Password))
{
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Overview");
}
ModelState.AddModelError("*", "The user name or password provided is incorrect.");
}
return View(model);
}
}
AccountServices.cs
public interface IMembershipService
{
bool ValidateUser(string userName, string password);
}
public class AccountMembershipService : IMembershipService
{
public bool ValidateUser(string userName, string password)
{
throw new System.NotImplementedException();
}
}
AccountControllerFacts.cs
public class AccountControllerFacts
{
public static AccountController GetAccountControllerForLogonSuccess()
{
var membershipServiceStub = MockRepository.GenerateStub<IMembershipService>();
var controller = new AccountController(membershipServiceStub);
membershipServiceStub
.Stub(x => x.ValidateUser("someuser", "somepass"))
.Return(true);
return controller;
}
public static AccountController GetAccountControllerForLogonFailure()
{
var membershipServiceStub = MockRepository.GenerateStub<IMembershipService>();
var controller = new AccountController(membershipServiceStub);
membershipServiceStub
.Stub(x => x.ValidateUser("someuser", "somepass"))
.Return(false);
return controller;
}
public class LogOn
{
[Fact]
public void Get_ReturnsViewResultWithDefaultViewName()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
// Act
var result = controller.LogOn();
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(((ViewResult)result).ViewName);
}
[Fact]
public void Put_ReturnsOverviewRedirectToRouteResultIfLogonSuccessAndNoReturnUrlGiven()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, null);
var redirectresult = (RedirectToRouteResult) result;
// Assert
Assert.IsType<RedirectToRouteResult>(result);
Assert.Equal("Overview", redirectresult.RouteValues["controller"]);
Assert.Equal("Index", redirectresult.RouteValues["action"]);
}
[Fact]
public void Put_ReturnsRedirectResultIfLogonSuccessAndReturnUrlGiven()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, "someurl");
var redirectResult = (RedirectResult) result;
// Assert
Assert.IsType<RedirectResult>(result);
Assert.Equal("someurl", redirectResult.Url);
}
[Fact]
public void Put_ReturnsViewIfInvalidModelState()
{
// Arrange
var controller = GetAccountControllerForLogonFailure();
var user = new LogOnModel();
controller.ModelState.AddModelError("*","Invalid model state.");
// Act
var result = controller.LogOn(user, "someurl");
var viewResult = (ViewResult) result;
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(viewResult.ViewName);
Assert.Same(user,viewResult.ViewData.Model);
}
[Fact]
public void Put_ReturnsViewIfLogonFailed()
{
// Arrange
var controller = GetAccountControllerForLogonFailure();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, "someurl");
var viewResult = (ViewResult) result;
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(viewResult.ViewName);
Assert.Same(user,viewResult.ViewData.Model);
Assert.Equal(false,viewResult.ViewData.ModelState.IsValid);
}
}
}
Figured out how to fix my tests.
[Fact]
public void Put_ReturnsRedirectToRouteResultForOverviewIfLogonSuccessAndNoReturnUrlGiven()
{
// Arrange
var mocks = new MockRepository();
var mockMembershipService = mocks.StrictMock<IMembershipService>();
using (mocks.Record())
{
Expect.Call(mockMembershipService.ValidateUser("", "")).IgnoreArguments().Return(true).Repeat.Any();
}
var controller = new AccountController(mockMembershipService);
var user = new LogOnModel();
// Act
ActionResult result;
using (mocks.Playback()){
result = controller.LogOn(user, null);
}
// Assert
Assert.IsType<RedirectToRouteResult>(result);
var redirectresult = (RedirectToRouteResult)result;
Assert.Equal("Overview", redirectresult.RouteValues["controller"]);
Assert.Equal("Index", redirectresult.RouteValues["action"]);
}

Unit Testing with ControllerActionInvoker to invoke action with parameters

I use ControllerActionInvoker to invoke controller actions form unit tests
var controllerInvoker = new ControllerActionInvoker();
var result = controllerInvoker.InvokeAction(
testController.ControllerContext, "Default" );
How do I use it to call an action that has parameters?
[AcceptVerbs( HttpVerbs.Post )]
[ActionException( SomeAttribute )]
public SomeResult AddMethod( long[] Ids )
{
//some code
}
From the documentation it looks like you want to use the InvokeActionMethod method which allows you to pass parameters in an IDictionary as the third argument.
The ControllerContext actually carries with it additional data that the controller will use for binding (filters, model binders, route data). Your argument will need to be passed through the ControllerContext.
I found an example about unit testing controllers.
You shouldn't use the ControllerActionInvoker from within your unit tests. What are you actually trying to accomplish?
If you're trying to test the behavior of your actions, just call them directly (they are just regular methods). If you're trying to test the behavior of your filters, create a mock context for the filter and call its OnXxx() method.
I use the ControllerActionInvoker because I want to write specification tests around my controllers rather than low level unit tests. What I have found is that my implementation of the ControllerActionInvoker has had to evolve based on what I am testing but so for the following has worked for me.
class ControllerSpecActionInvoker<TResult> : ControllerActionInvoker where
TResult : ActionResult
{
private readonly Expression body;
public ControllerSpecActionInvoker(Expression body)
{
this.body = body;
}
public TResult Result { get; private set; }
protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
=> Result = actionResult as TResult;
protected override IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (body is MethodCallExpression methodCall)
{
return methodCall.Method.GetParameters()
.Zip(methodCall.Arguments.Select(GetArgumentAsConstant), (param, arg) => new { param.Name, Value = ChangeType(arg.Value, param.ParameterType) })
.ToDictionary(item => item.Name, item => item.Value);
}
return base.GetParameterValues(controllerContext, actionDescriptor);
}
private ConstantExpression GetArgumentAsConstant(Expression exp)
{
switch (exp)
{
case ConstantExpression constExp:
return constExp;
case UnaryExpression uranExp:
return GetArgumentAsConstant(uranExp.Operand);
}
throw new NotSupportedException($"Cannot handle expression of type '{exp.GetType()}'");
}
private static object ChangeType(object value, Type conversion)
{
var t = conversion;
if (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(Nullable<>)) return Convert.ChangeType(value, t);
if (value == null) return null;
t = Nullable.GetUnderlyingType(t);
return Convert.ChangeType(value, t);
}
}
For my purposes this is used in specification fixture base class and auto mock dependencies but the essence of how you use it is like this:
var actionInvoker = new ControllerSpecActionInvoker<ActionResult>(Expression<Func<ActionResult|JsonResult|Etc>>);
actionInvoker.InvokeAction(<controller context>, <name of the action>);
Result = actionInvoker.Result;
So it might look something like this which is not test. Most of the cruft can be hidden away in a base class:
class MyController : Controller
{
JsonResult MyAction(int i) { return Json(new {}); }
}
class MyControllerFixture
{
[Test]
public void ReturnsData()
{
var controller = new MyController();
var controllerContext = new ControllerContext
{
RouteData = new RouteData(),
HttpContext = httpContextBase,
};
controllerContext.Controller = controller;
controller.ControllerContext = controllerContext;
Action<JsonResult> act = controller.MyAction(1);
var actionInvoker = new ControllerSpecActionInvoker<JsonResult>(act.Body);
actionInvoiker.Result.Should().NotBeNull();
}
}

Resources