I am building a test for an MVC5 controller method. I'm using moq for the test. What I'm interested in is how to test a controller method that requires authentication and uses the userid value not the username value to make decisions about what data to show to the browser/client.
From what I have researched so far, there is a considerable amount of code available to moq the username, but not much code for the userid value (in my case that looks like a Guid). I'm using ASP.Net Identity as the account management. I have OWIN added as well for Google and Facebook login.
I'm using dependency injection (using Unity) because it seems that that is about the only way to accomplish tests, plus DI enables the use of moq.
Here is an example of the test method that is looking for a 'NotNull' return from the controller method:
Mock<ModelObject> CreateModelObjectFromHelper()
{
var ci = new Mock<ClaimsIdentity>();
var myHelper = new Mock<MyHelper>();
myHelper.Setup(x => x.GetCurrentUserId(ci.Object)).Returns("333c188b-b33a-4233-83bd-5ea3a3333333");
return new Mock<ModelObject>(myHelper.Object);
}
[TestMethod]
public async Task ExampleController_Method_NotNull()
{
Mock<ModelObject> o = CreateModelObjectFromHelper();
ExampleController controller = new ExampleController(o.Object as IModelObject);
ViewResult result = await controller.MethodName() as ViewResult;
//check for non null result
Assert.IsNotNull(result);
}
The GetCurrentUserId method is where I query the User.Identity object for the userid using this code:
var userIdClaim = user.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
userIdValue = userIdClaim.Value.ToString();
And the method is supposedly getting replaced by moq. The GetCurrentUserId method is a virtual method in a helper class (MyHelper) that is the parameter of the constructor for the model object (ModelObject) that is the parameter of the constructor for the controller (ExampleController).
What happens in the debugger (Visual Studio 2013) for the test method is that the objects are created in the CreateModelObjectFromHelper method, but when the ExampleController is created at the line:
ExampleController controller = new ExampleController(o.Object as IModelObject);
The MyHelper moq'd object becomes null. I can see the object has a value before this statement, but at this statement, the MyHelper object becomes null...and the whole test fails. I assume it fails because it is difficult to call methods on an object that is null.
The question is...what causes this object to become null?
The other question is...maybe there is a better way to do this and if so, I'd sure be glad to hear a suggestion.
Thank you
It doesn't work like that. You try to create mock of a mock of a mock ...
If your controller depend only on the IModelObject, then only create Mock and set it's properties/methods to return what you need in the controller.
Without the code for IModelObject, and how it is used in the controller, it's hard to provide better example.
Related
I have an asp.net core 2.2 mvc project setup with windows authentication,
my razor layout has this #User.Identity.Name which works great, but in my controller, User.Identity is null! any idea what I am doing wrong?
here's a sample of my controller:
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using UI.Models;
namespace UI.Controllers
{
public class HomeController : Controller
{
string shortName = string.Empty;
public HomeController()
{
shortName = User.Identity.Name;
}
public IActionResult Index()
{
return View();
}
}
}
You cannot access User, HttpContext, or other related properties within the constructor of a controller. That means that you can access these properties only within an actual controller action, like your Index method.
To understand this, you need to understand how these controller properties actually work and how the framework will manage the lifetime of a controller. As you may be aware, controllers are short-lived objects that are created temporarily only for the duration of a single request. That means that they will always execute within the scope of a single request.
By that logic, accessing the user or other properties of the HTTP context should work just fine; and it usually is. However, when you inherit those properties from Controller or ControllerBase, then those don’t magically have a value. Instead, they will be set explicitly by the framework after it creates the controller.
The logic for this is simlar to this:
// create the controller with its dependencies
var controller = CreateController<HomeController>();
// set the controller context
controller.ControllerContext = context;
// invoke action
controller.Index();
So when the object is constructed and the constructor runs, you can access anything that is a direct dependency of the controller, since the dependency injection container will provide all those values through constructor injection.
Anything else though will not be available automatically. That includes all the default properties that you inherit from Controller or ControllerBase. These are usually implicitly set by the ControllerContext, so you will have to wait for the framework to set those.
Since it is impossible to set a property value before the constructor runs, you will simply not be able to access any of these values within the constructor. So the solution is to move the logic into an action instead. That way, you can access the properties and they will have the proper values.
public class HomeController : Controller
{
public IActionResult Index()
{
var shortName = User.Identity.Name;
return View();
}
}
I just wrote my first unit test and now I have some questions. Here is said test:
[Test]
public void IndexShouldReturnPosts()
{
// arrange
var repository = Mock.Create<IRepository>();
var posts = new []
{
new Post { Title = "Hello", Slug = "hello", Content = "Some post."},
new Post { Title = "Goodbye", Slug = "goodbye", Content = "Some post."}
};
Mock.Arrange(() => repository.GetAll()).Returns(posts);
var controller = new HomeController(repository);
// act
var result = controller.Index();
var model = (Post[]) result.Model;
// assert
CollectionAssert.AreEqual(posts, model);
}
This test passes, and I understand why. However, the web page does not actually work as expected as no view cannot be found.
I think (and please correct me if I am wrong) that I need to write another test to validate that view is rendered correctly but I do not know how.
How can I test that my view is rendered correctly?
You can test a controller action which returns a ViewResult (which is what you're trying to do, I think) like so:
var myController = new MyController([mocked dependencies here]);
myController.ControllerContext = mockedControllerContext;
var result = myController.MyActionWhichReturnsAViewResult();
Assert.IsNotNull(result);
Assert.IsInstanceOf<ViewResult>(result);
Assert.That(result.ViewName == [expectedViewName])
Which is to say, you'll need to mock up not just the dependencies of your controller (if it needs data access, etc.), but also the HTTP context in which the controller is intended to live. The Moq library makes this pretty easy, and here's another Stack Overflow question which may help you with it: How do I mock the HttpContext in ASP.NET MVC using Moq?
Unit tests test...a single unit. If you are trying to test the controller and the view at the same time, it is not a unit test. If you want to do an integration test and get the actual html that is produced, check out http://blog.stevensanderson.com/2009/06/11/integration-testing-your-aspnet-mvc-application/
Edit: You could probably make a unit test for the view provided you mocked/faked the controller and the model, but it might get a bit complicated because you would also need to mock the ControllerContext (see Mocking Asp.net-mvc Controller Context). Once that is all set up, you could render the view into html using something like http://codetunnel.com/how-to-render-an-aspnet-mvc-view-to-a-string-from-within-the-controller. Just use FindView instead of FindPartialView. If there are any errors in your cshtml, an exception will be thrown which you can detect. You can also inspect the html text string to see if the elements/data you are expecting are present.
I would consider doing web testing instead if you want to test that the correct view is rendered. My framework of choice is Selenium, but there are others available.
It let's you automate a browser through a rich c# API
I'm building the unit tests for a C# MVC5 Project using Entity Framework 6. I'm trying to mock my BlogRepository using Moq which will then be used as an argument for the BlogController which I am attempting to test. I actually have the unit test working fine, but to do this I created a Fake BlogRepository Class, when I would much rather work out how to do it using Moq.
The problem I'm getting is that the Controller wants the argument to be of type IBlogRepository but is only seeing it as Mock. So I get an invalid arguments error. I thought this was how it was meant to be used though.
Here is my attempt at creating the mock:
Mock<IBlogRepository> blogRepo = new Mock<IBlogRepository>();
blogRepo.Setup(t => t.GetBlogByID(It.IsAny<int>())).Returns<Blog>(blog => new Blog());
And here is the beginning of the controller:
public class BlogController : Controller
{
IBlogRepository blogRepo;
public BlogController(IBlogRepository repoBlog)
{
blogRepo = repoBlog;
}
What am I doing wrong? or have I got the wrong idea here. Any help would be appreciated. Thanks.
You should pass blogRepo.Object not blogRepo to your controller.
I have been unsuccessful in attempting to set a session variable in order to run some unit tests. I keep getting the error of "System.NullReferenceException: Object reference not set to an instance of an object" when I attempt to set my session variable.
Here is the test I'm building:
[TestMethod]
public void MyMethod()
{
//Arrange
int id = 12345;
string action = "A";
string comment = "";
string user = "user";
var controller = new MyController();
//Act
controller.Session["altUser"] = user;
var result = controller.Process(id, action, comment);
//Assert
Assert.IsNotNull(result);
}
And here is my controller:
[Authorize]
public class MyController : Controller
{
public ActionResult Process(int id, string action, string comment)
{
string userId = Session["altUser"].ToString();
//some other stuff that evaluates ID, Action, and Comment
}
}
However, when I run the application itself, there are no errors and the application functions as it should. I do understand that with Test Driven Development the tests should pave the way for the implementation. I am trying to get some practice with Unit Testing on applications that have already been done. If at all possible, since the application works, I would like to avoid making any changes to my implementation and just write a unit test to support what I already know.
The controller gets the session from the HttpContext, which does not exist in your unit test, which is why it fails.
You can, however, mock an HttpContext and put a mock session in there too.
Something like this might work (uses moq as the Mocking framework)
var mockControllerContext = new Mock<ControllerContext>();
var mockSession = new Mock<HttpSessionStateBase>();
mockSession.SetupGet(s => s["altUser"]).Returns("user");
mockControllerContext.Setup(p => p.HttpContext.Session).Returns(mockSession.Object);
var controller = new MyController();
controller.ControllerContext = mockControllerContext.Object;
You'll obviously need to fill the mock object with the details of what you actually want to get out.
You can also derive your own classes from HttpSessionStateBase and HttpContextBase and use them instead of the real session.
There is no spoon, er, session — the ASP.NET runtine is neither initialized nor running when you execute the unit test. You should decompose any Session dependencies outside your code and make the classes and methods you need to unit test independent of ASP.NET's runtime features.
When you run the unit test, you're not running the program via a web server, and therefore do not have access to any HttpContext, or ASP.NET session, whereas when you run the program via debug in VS, you are using the built in Visual Studio web server, which enables the program to use the session.
Here is an article which walks you through the ASP.NET session and how it works.
I have my own custom Authorize Attribute and I am trying to check my controller methods to see if they have the correct roles in place. Now my custom authorize tag has database code in it.
The ways I am mocking it up don't seem to work since the reflection stuff I found seems to to just pass no arguments so my default constructor in the Authorize Attribute gets hit creating a new service layer object that creates a repository object(that kills the unit test).
var indexAction = typeof(Controller).GetMethod(method);
var authorizeAttributes = indexAction.GetCustomAttributes(typeof(AuthorizeAttribute), true);
//Assert
Assert.That(authorizeAttributes.Length > 0, Is.True);
foreach (AuthorizeAttribute att in authorizeAttributes)
{
Assert.That(att.Roles, Is.EqualTo(roles));
}
Constructors of my AutorizeAttribute
public MyAuthorize()
{
authorize = new ServiceLayer();
}
public MyAuthorize(IServicelayer layer)
{
authorize = layer;
}
the reflection stuff keeps calling my default constructor. How can I pass in a mock service layer or something?
Thanks
Have you looked at some of the Mocking Frameworks? I've used these to fake the http context etc in the past.
Here's another Stack Overflow post that might be able to help you...
https://stackoverflow.com/questions/37359/what-c-mocking-framework-to-use
I don't think the problem is with your code but what you are trying to test. What determines the roles that the attribute has?
If you are retrieving the roles from your service layer based on something passed into the attribute, your tests should confirm that the attribute exists on the action it is protecting ( part of the controller tests ), the appropriate calls are made to your service layer from the attribute ( part of the attribute tests ), and that the service layer returns the appropriate values for a specific request ( part of the controller tests ).
To ensure all of the parts work together, you will need to use integration tests that essentially mimic the entire request pipeline - something like Steve Sanderson's MvcIntegrationTest should simplify this http://blog.codeville.net/2009/06/11/integration-testing-your-aspnet-mvc-application/