Some of my controller actions need to respond with different ViewResults depending whether or not they were called by an AJAX request. Currently, I'm using the IsAjaxRequest() method to check for this. When this method is called during a unit test, it throws an ArgumentNullException because the HTTP context is missing.
Is there a way to mock/fake this call? Or is this a sign I should be checking for an AJAX request another way?
Would it help if you provide a Test Double for the HTTP Context?
This can be done like this:
var httpCtxStub = new Mock<HttpContextBase>();
var controllerCtx = new ControllerContext();
controllerCtx.HttpContext = httpCtxStub.Object;
sut.ControllerContext = controllerCtx;
where sut represents the System Under Test (SUT), i.e. the Controller you wish to test.
This example uses Moq.
Using moq library in MVC test projects
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void Index()
{
// Arrange
HomeController controller = new HomeController();
controller.injectContext();
// controller.injectContext(ajaxRequest: true);
// Act
ViewResult result = controller.Index() as ViewResult;
// Assert
Assert.IsNotNull(result);
}
}
public static class MvcTestExtensions
{
public static void injectContext(this ControllerBase controller, bool ajaxRequest = false)
{
var fakeContext = new Mock<ControllerContext>();
fakeContext.Setup(r => r.HttpContext.Request["X-Requested-With"])
.Returns(ajaxRequest ? "XMLHttpRequest" : "");
controller.ControllerContext = fakeContext.Object;
}
}
Related
I have the following controller action
[Route(ActionName.Create)]
public async Task<ActionResult> Create()
{
....
if (!UserContext.Roles.HasFlag(GroupsEnum.Admin))
{
throw new UnauthorizedAccessException("Permission Denied");
}
...
}
I have following in xxxxControllerTests.cs
[TestMethod]
public async Task Can_Request_Create()
{
ViewResult result = (ViewResult)await MockController.Object.Create();
...
...
}
when I run the unit test I get unauthorized access exception raised. How can pass UserContext while mockController, so controller action will be executed with specific user context?
Normally when you're instantiating the Controller you're testing, you can initialize it like this:
var controllerToTest = new MyController() {
ControllerContext = new ControllerContext {
HttpContext = //assign mock context,
UserContext = //assign mock context
}
};
var result = controllerToTest.Create();
However this line of code makes me think you're testing an already mocked controller:
ViewResult result = (ViewResult)await MockController.Object.Create();
The .Object call makes me think you have a Mock<MyController> in there and that's not right. You're not testing your own controller.
You should Mock your dbContext.
[https://stackoverflow.com/questions/54219742/mocking-ef-core-dbcontext-and-dbset][1]
In my MVC project I had to setup a controller class attribute:
public class ResponsableAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var controller = (BaseController)filterContext.Controller;
if (!controller.EstResponsable)
{
filterContext.Result = controller.RedirectionForcee(Alertage.Information, ConstantesUi.Misc.MessageResponsable);
}
}
}
Bear with me, the code's in french:
[Responsable]
public class ParamLivraisonController : BaseController
So every action has to pass through the attribute before being executed, which is the intended behavior.
But I do not know how to unit test this attribute, set the return value as false so that the controller.RedirectionForcee (which returns a RedirectToRouteResult) actually does its job.
If I call any controller method:
[TestMethod]
public void ParamLivraisonController_GererLivraison_Get()
{
ControleurAsynchrone.ObtenirLivraisons().Returns(ObtenirListeLivraison());
var retour = _ctrl.GererLivraisons();
Assert.IsNotNull(retour);
}
The attribute is not hit or tested. How can test it? I am using nSubstitute with mvcFakes and I do not know how to substitute ActionExecutingContext.
Thanks to #Kenneth K., I found the answer. #Nkosi was right as well to mention that the test should be on the attribute itself.
Here it is:
[TestMethod]
public void ValiderAttribut_EstResponsable()
{
var attribut = new ResponsableAttribute(); // Instantiation of the attribute
var controller = ObtenirController(); // Gets the controller via MvcFakes
SecuriteHelper.VerifierResponsable().Returns(true); // Sets the desired return value
var test = Substitute.For<ActionExecutingContext>(); // Substitute for ActionExecutingContext
test.Controller = controller; // Sets the controller to the context
attribut.OnActionExecuting(test); // Call the overrided method
Assert.IsNull(test.Result); // Check if the redirection occured
}
Now, to validate if the test was right, I also had to test the opposite result:
[TestMethod]
public void ValiderAttribut_EstNonResponsable()
{
var attribut = new ResponsableAttribute();
var controller = ObtenirController();
SecuriteHelper.VerifierResponsable().Returns(false);
var test = Substitute.For<ActionExecutingContext>();
test.Controller = controller;
attribut.OnActionExecuting(test);
Assert.IsNotNull(test.Result);
Assert.AreEqual(typeof(RedirectResult), test.Result.GetType());
}
And it worked. Thanks!
I have an Area in my MVC site. This area has the typical Controller/Model/View setup.
As a controller I have the following code:
public class DocumentCreatorController : Controller
{
// GET: Templates/DocumentCreator
public ActionResult OfferTemplate(BaseDocumentViewModel data)
{
return this.Pdf(nameof(OfferTemplate), data, "File.pdf");
}
}
The method this.Pdf does a couple of stuff, but the interesting is it comes down to the ViewEngine call:
var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);
Here I call the FindPartialView with a ControllerContext and a PartialViewName. My PartialViewName comes from the nameof(OfferTemplate) from the controller action OfferTemplate. I think the controllercontext is my challenge.
My challenge:
When I want to set this up in a unit test (using Moq), I have the following code based on pages such as Mocking The RouteData Class in System.Web.Routing for MVC applications and Mocking Asp.net-mvc Controller Context:
[TestMethod]
public void OfferTemplate()
{
var ctr = SetupControllerWithContext();
}
private static DocumentCreatorController SetupControllerWithContext()
{
var routeData = new RouteData();
routeData.Values.Add("controller", "DocumentCreatorController");
routeData.Values.Add("action", "OfferTemplate");
var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object
, routeData, new Mock<ControllerBase>().Object);
DocumentCreatorController ctr = new DocumentCreatorController();
ctr.ControllerContext = controllerContext;
return ctr;
}
Which gives the following error:
Eesy.Websites.Api.Tests.Controllers.DocumentCreatorControllerTest.OfferTemplate
threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
This I don't understand.
My folder setup:
Debug image on ControllerContext on calling the FindPartialView:
Anyone have an idea?
Is it because I setup the RouteData wrong?
You are trying to mock and test framework code. Abstract that functionality out into code you control so you can test in isolation if needed.
Currently the action and by extension the controller is tightly coupled to external 3rd party dependencies. If the goal was to test the controller action flow in isolation then it is advised to abstract out the 3rd party PDF generation so that it can be mocked for easier testability.
public interface IDocumentService {
ActionResult ToPdf(Controller arg1, string arg2, object arg3, string arg4);
}
The controller would explicitly depend on this abstraction via constructor injection.
public class DocumentCreatorController : Controller {
private readonly IDocumentService render;
DocumentCreatorController(IDocumentService render) {
this.render = render;
}
// GET: Templates/DocumentCreator
public ActionResult OfferTemplate(BaseDocumentViewModel data) {
return render.ToPdf(this, nameof(OfferTemplate), data, "File.pdf");
}
}
So now to test the controller's pdf generation process you need only mock your abstraction.
[TestMethod]
public void OfferTemplate() {
//Arrange
var serviceMock = new Mock<IDocumentService>();
//...setup mock for use case
var controller = new DocumentCreatorController(serviceMock.Object);
var data = new BaseDocumentViewModel {
//...
};
//Act
var actual = controller.OfferTemplate(data);
//Assert
//...assert behavior
}
The actual implementation of the service would encapsulate the actual functionality and would be registered with the dependency injection container along with the abstraction.
To test the actual generation you would need to do an integration test which is another topic.
So I'm posting to an MVC controller, which makes a call to a repository to get a Telerik report, then exports a PDF. I'm having trouble unit testing this and keep getting an error -
System.NullReferenceException: Object reference not set to an instance of an object.
Controller
public class ReportController : Controller
{
private IPDFRepository _pdfRepository;
//Dependency Injection using Unity.MVC5 NuGet Package
public ReportController(IPDFRepository pdfRepository)
{
_pdfRepository = pdfRepository;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PDFExport(PDFViewModel model)
{
byte[] report = _pdfRepository.BuildExport(model);
return File(report, "application/pdf", model.SelectedReport + ".pdf");
}
}
Unit Test
[TestMethod]
public void Report_PDFExport_Returns_ActionResult()
{
//Arrange
var mockRepository = new Mock<IPDFRepository>();
mockRepository.Setup(x => x.BuildExport(It.IsAny<PDFViewModel>()));
ReportController controller = new ReportController(mockRepository.Object);
//Act
ActionResult result = controller.PDFExport(It.IsAny<PDFViewModel>());
//Assert
Assert.IsInstanceOfType(result, typeof(ActionResult));
}
Now, I realize this has something to do with this return portion of my controller.
return File(report, "application/pdf", model.SelectedReport + ".pdf");
I can change that around to return string, test again and get this to work.
Also, if I comment out these last two lines of the unit test,
//Act
//ActionResult result = controller.PDFExport(It.IsAny<PDFViewModel>());
//Assert
//Assert.IsInstanceOfType(result, typeof(ActionResult));
it will run without error. I can't figure out how to get around the null reference.
You are not setting up the mock of IPDFRepository properly. It needs to configure what it is going to return when BuildExport is called. Otherwise report will be null.
And you are also not calling the method under test with a valid parameter. You need to create a concrete instance other wise the model will be null and model.SelectedReport with error out.
[TestMethod]
public void Report_PDFExport_Returns_ActionResult()
{
//Arrange
byte[] fakePDFReport = new byte[0];
var mockRepository = new Mock<IPDFRepository>();
mockRepository
.Setup(x => x.BuildExport(It.IsAny<PDFViewModel>()))
.Returns(fakePDFReport);
var fakeViewModel = new PDFViewModel {
SelectedReport = "FakeReportName"
//Set the needed properties...
};
ReportController controller = new ReportController(mockRepository.Object);
//Act
ActionResult result = controller.PDFExport(fakeViewModel);
//Assert
Assert.IsInstanceOfType(result, typeof(ActionResult));
}
I see there are a lot of questions about this same topic, but since they are all from 2008 - 2011, I'd say there's a chance this might be an official way to do this without external libraries/extensions.
So the issue is when running my test cases, the ViewName comes empty:
// Act
ViewResult result = await Controller.Create(model) as ViewResult;
// Assert
Assert.AreEqual("Create", result.ViewName);
Any official way to deal with this? or maybe I can test some other property?
If your Controller Method does just
return View();
without a view name parameter value given, you will not have the name of the view in the ViewName property. For Unit Testing Controllers read: https://msdn.microsoft.com/en-us/library/ff847525(v=vs.100).aspx
How to create a controller with ControllerContext:
HomeController controller = new HomeController(repository);
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = new RequestContext(new MockHttpContext(), new RouteData())
};
With:
private class MockHttpContext : HttpContextBase {
private readonly IPrincipal _user = new GenericPrincipal(
new GenericIdentity("someUser"), null /* roles */);
public override IPrincipal User {
get {
return _user;
}
set {
base.User = value;
}
}
}