I'm trying to write a Unit test that checks two controller actions. These controller actions rely on Session variables. So far I have this:
Unit Test:
[TestMethod]
public void TableOfContentReportTest()
{
// Arrange
var fakeDb = TestFakes.SetupFakeDbContext();
var controller = TestFakes.ProjectController(fakeDb);
// Act
var selectedSubs = AutoMapper.Mapper.Map<ProjectSubmissionViewModel>(fakeDb.ProjectSubmission.FirstOrDefault());
selectedSubs.Selected = true;
controller.Session["SelectedSubmissions"] = new List<ProjectSubmissionViewModel> {selectedSubs};
var result = controller.SubmissionIndex("ProjectTitle", true,1, 10,"","","","","",
StaticStrings.Report_TableOfContents) as ViewResult;
// Assert
Assert.IsNotNull(result);
var testSession = controller.processReport();
}
TestFakes.ProjectController sets up the session for the controller like this:
//...
var session = new Mock<HttpSessionStateBase>();
var context = new Mock<HttpContextBase>(MockBehavior.Strict);
context.SetupGet(x => x.Session).Returns(session.Object);
var rc = new RequestContext(context.Object, new RouteData());
var controller = new ProjectController(fakeDb);
controller.ControllerContext = new ControllerContext(rc, controller);
//...
SubmissionIndex sets the Session variable:
public virtual ActionResult SubmissionIndex(
string sortBy = "ProjectTitle",
bool ascending = true,
int page = 1,
int pageSize = 10,
string projectId = "",
string submissiontitle = "",
string firstname = "",
string lastname = "",
string email = "",
string showreport = "")
{
var selectedSubmissions = Session["SelectedSubmissions"] as ICollection<ProjectSubmissionViewModel>;
//... Uses selectedSubmissions to build bookResources and chapters
Session["reportData"] = viewModel.GetContentsReport(bookResources, chapters);
//...
}
At runtime, the code works. With the unit test, SubmissionIndex sees Session["SelectedSubmissions"] as null.
Am I setting up the fake controller's session wrong? How can I work with the Session while testing?
Update: I often call the SubmissionIndex action from a Redirect:
Session["SelectedSubmissions"] = model.Submissions.Where(s => s.Selected).ToList();
return RedirectToAction("SubmissionIndex", "Project", routeValues);
Have you tried mocking it a bit more directly:
var session = new Mock<HttpSessionStateBase>();
var myCollection = new ICollection<ProjectSubmissionViewModel> { object1, object2... etc };
session.Setup(x => x["SelectedSubmissions"]).Returns(myCollection);
Or you could even return based on a generic input
session.Setup(x => x[It.IsAny<string>()]).Returns(myCollection);
Example
private ProjectController controller;
[TestSetup]
public void Setup()
{
var dbMock = new Mock<db>();
//Set up properties etc
var context = new Mock<HttpContextBase>(MockBehavior.Strict);
context.SetupGet(x => x.Session["SelectedSubmissions"]).Returns(object1);
context.SetupGet(x => x.Session["reportStuff"]).Returns(object2);
controller = new ProjectController(dbMock.Object);
}
[TestMethod]
public void TableOfContentReportTest()
{
var result = controller.SubmissionIndex(
"ProjectTitle",
true,
1, 10,"","","","","",
StaticStrings.Report_TableOfContents) as ViewResult;
// Assert
Assert.IsNotNull(result);
var testSession = controller.processReport();
}
Related
I have ASP.NET MVC Project and I have some module. Some modules have pagination. For test and understand MvcSiteMapProvider I working with one module Forum and created ForumDynamicNodeProvider class
public class ForumDynamicNodeProvider : DynamicNodeProviderBase
{
private readonly IForumsService _forumsService;
public ForumDynamicNodeProvider(IForumsService forumsService)
{
this._forumsService = forumsService;
}
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
string rootTitle = ManagerLocalization.Get("Forums", "FORUMS");
var nodes = new List<DynamicNode>
{
new DynamicNode
{
Title = rootTitle,
Controller = "Forums",
Action = "Index",
Key = "forum_home"
}
};
var forums = this._forumsService.GetForums<ForumNode>().ToList();
var topics = this._forumsService.GetTopics<TopicNode>().ToList();
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue
});
}
foreach (var topic in topics)
{
var forum = forums.FirstOrDefault(item => item.Id == topic.ForumId);
var forumRouteValue = new Dictionary<string, object> { { "forum", forum.NameTranslit }, { "topicName", topic.TitleTranslite }, {"page", 0 } };
nodes.Add(new DynamicNode
{
Key = $"topic_{topic.Id}",
ParentKey = $"forum_{topic.ForumId}",
Title = topic.Title,
Controller = "Forums",
Action = "ShowTopic",
RouteValues = forumRouteValue
});
}
return nodes;
}
private ForumNode GetParentForum(List<ForumNode> forums, ForumNode forum)
{
if (forum.ForumId > 0)
{
return forums.FirstOrDefault(item => item.Id == forum.ForumId);
}
return null;
}
}
But I can't found a good decision for pagination. For easy I can use page prefix for key and make duplicate DynamicNode. But it's bad idea, because when I have example 1000 topics and each topic have 20 page I must create 20000 DynamicNode. Maybe have other decision?
For ambient context (such as page number) you can use PreservedRouteParameters to force a match on any value for the specified keys. These keys match either route values or query string parameters from the request (route values take precedence if they are the same).
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
// Always match the "page" route value regardless of its value
var forumPreservedRouteParameters = new List<string>() { "page" };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue,
PreservedRouteParameters = forumPreservedRouteParameters
});
}
NOTE: When you use PreservedRouteParameters, they are included in the generated URL from the current request if provided and not included in the URL if not provided in the request. Therefore, if you have more than one page number in the same ancestry you need to have a separate route key name for each one or the current page number will be passed to the ancestor nodes from the current request.
I have an ActionResult like this:
public ActionResult AddDoc(StudentModel studentModel)
{
var student = _studentHelper.GetStudent(studentModel, true);
_updateStudentManager.UpsertStudent(student);
return Json(new { result = true });
}
Test method:
[TestMethod]
public void Calling_AddDoc_Returns_JsonResult()
{
var studentModel = new StudentModel()
{
Name = "Jon",
Id = "1"
};
var studentToAdd = new Student()
{
StudentId = "1",
Name = "Jon",
Course = "SomeCourse"
};
_studentHelper.Setup(x => x.GetStudent(studentModel, false)).Returns(studentToAdd);
var res = _controller.AddDoc(studentModel) as JsonResult;
Assert.AreEqual("{ result = True }", res.Data.ToString());
}
The GetStudent() just maps the incoming object to a new instance of Student and returns it.
The test fails with 'System.NullReferenceException'
Debugging the TestMethod shows that this line
var student = _studentHelper.GetStudent(studentModel, true);
in the controller is not executing at all even when I tried to Step Into the method.
The 'student' object is null.
What am I doing wrong?
Thanks in advance.
Your Setup call has an error. This call expects second parameter to be false.
_studentHelper.Setup(x => x.GetStudent(studentModel, false)).Returns(studentToAdd);
While you are calling from the controller with the value of true.
var student = _studentHelper.GetStudent(studentModel, true);
You are probably using loose behavior of Moq which makes it return null.
I have this function and unit test.
ProfileController Code
[HttpPost]
public ActionResult Edit(int? id)
{
var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "Profile", new { id = 0 });
return Json(new { Url = redirectUrl });
}
unit test code
[TestMethod]
public void TestDetailsViewData()
{
var controller = new ProfileController(_Profile);
var result = controller.Edit(1) as ViewResult;
var profile = (VIEWMODELS.Customer.Profile)result.ViewData.Model;
Assert.AreEqual("Testor", profile.cardName);
}
i would like to test this function and this function will redirect to index page and return ViewPage with data. But the problem is when i run this unit test code i got Null exception at this line ,because of the Request is NULL
var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "Profile", new { id = 0 });
so may i know how could i test this?
Updated with Moq function
After further reading and get some sample code from here it seem request able to filled with value but now the RequestContext is NULL, anyone can point me to right place for me to study?
[TestMethod]
public void TestDetailsViewData()
{
var request = new Mock<HttpRequestBase>();
request.SetupGet(x => x.Headers).Returns(
new System.Net.WebHeaderCollection {
{"X-Requested-With", "XMLHttpRequest"}
});
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
//context.SetupGet(x => x.Request.RequestContext ).Returns(request.Object.RequestContext);
var controller = new ProfileController(_Profile);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
controller.Request.RequestContext = context.Object.Request.RequestContext;
var result = controller.Edit(1) as ViewResult;
var profile = (VIEWMODELS.Customer.Profile)result.ViewData.Model;
Assert.AreEqual("testor", profile.cardName);
}
The error is occurring because the request is not defined in Test method.I think you have to use visual studio fakes.
https://msdn.microsoft.com/en-us/library/hh549175.aspx
This is my Action for my Login Controller. I am able to mock the action using Moq.
But i get an error when it hits this.HttpContext.Response.Cookies.Set(new HttpCookie("AcceptedDoDNotice") { Expires = DateTime.Now.AddDays(-1) });
Error:Additional information: Object reference not set to an instance of an object.
How do i Mock the cookie so i wont get the error above?
public ActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = _uow.UserRepository.FindLogin(model.Email, model.Password);
if (user != null)
{
_uow.UserRepository.UpdateLastLoginDate(user);
_uow.SaveChanges();
this.HttpContext.Response.Cookies.Set(new HttpCookie("MyCookie") { Expires = DateTime.Now.AddDays(-1) });
if (user.UserLevel.IsAdmin)
return RedirectToAction("Index", "Administrator");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
The Moq test is below:
//Arrange
var model = new LoginViewModel();
model.Email = realEmail;
model.Password = realPassword;
//Act
var loginController = new LoginController();
var result = loginController.Login(model) as RedirectToRouteResult;
var actual = result.RouteValues["controller"].ToString();
//Assert
actual.Should().Be("Administrator");
You need to mock the controller context. This will allow you to provide a mocked response which in turn provides a cookie collection that you control. You can then see exactly which cookies were set on the response.
var cookieCollection = new HttpCookieCollection { };
var response = new Mock<HttpResponseBase>();
response.SetupGet(r => r.Cookies).Returns(cookieCollection);
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Response).Returns(response.Object);
var loginController = new LoginController();
loginController.ControllerContext = new ControllerContext(context.Object, new RouteData(), loginController);
var model = new LoginModel { };
var result = loginController.Login(model) as RedirectToRouteResult;
var actual = result.RouteValues["controller"].ToString();
//Assert
actual.Should().Be("Administrator");
cookieCollection.Should().HaveCount(1);
// other assertions about your cookie
I am trying to mock the Ajax.IsRequest() method of ASP.Net MVC. I found out how to do it in order for it to return true:
Expect.Call(_myController.Request.Headers["X-Requested-With"]).Return("XMLHttpRequest").Repeat.Any();
This works and returns true. Now I need to test the other branch of the code. How can I mock it to return false? I have tried removing the mock altogether, It fails with:
System.NullReferenceException : Object
reference not set to an instance of an
object.]
If I do:
Expect.Call(_templateReportController.Request["X-Requested-With"]).Return(null).Repeat.Any();
It fails with the same error.
Entire Test:
/// <summary>
/// Tests the Edit Action when calling via Ajax
/// </summary>
[Test]
public void Test_Edit_AjaxRequest()
{
Group group = new Group();
group.ID = 1;
group.Name = "Admin";
IList<Group> groupList = new List<Group>() { group };
Definition def = new Definition();
def.ID = 1;
def.Name = "Report";
def.LastModified = DateTime.UtcNow;
def.Groups.Add(group);
using (_mocks.Record())
{
Expect.Call(_myController.Request["X-Requested-With"]).Return("XMLHttpRequest").Repeat.Any();
Expect.Call(_DefBiz.GetAll<Group>()).Return(groupList);
Expect.Call(_DefBiz.Get<Definition>(1)).Return(def);
}
myController.DefAccess = _DefBiz;
PartialViewResult actual;
using (_mocks.Playback())
{
actual = (PartialViewResult)myController.Edit(1);
}
}
Any advices?
Cheers
The reason your are getting NullReferenceException is because you never stubbed the controller.Request object in your unit test and when you invoke the controller action which uses Request.IsAjaxRequest() it throws.
Here's how you could mock the context using Rhino.Mocks:
[TestMethod]
public void Test_Ajax()
{
// arrange
var sut = new HomeController();
var context = MockRepository.GenerateStub<HttpContextBase>();
var request = MockRepository.GenerateStub<HttpRequestBase>();
context.Stub(x => x.Request).Return(request);
// indicate AJAX request
request.Stub(x => x["X-Requested-With"]).Return("XMLHttpRequest");
sut.ControllerContext = new ControllerContext(context, new RouteData(), sut);
// act
var actual = sut.Index();
// assert
// TODO: ...
}
[TestMethod]
public void Test_Non_Ajax()
{
// arrange
var sut = new HomeController();
var context = MockRepository.GenerateStub<HttpContextBase>();
var request = MockRepository.GenerateStub<HttpRequestBase>();
context.Stub(x => x.Request).Return(request);
sut.ControllerContext = new ControllerContext(context, new RouteData(), sut);
// act
var actual = sut.Index();
// assert
// TODO: ...
}
And here's a better alternative (which I would personally recommend you) in order to avoid all the plumbing code. Using MVCContrib.TestHelper (which is based on Rhino.Mocks) your unit test might be simplified to this:
[TestClass]
public class HomeControllerTests : TestControllerBuilder
{
private HomeController _sut;
[TestInitialize()]
public void MyTestInitialize()
{
_sut = new HomeController();
this.InitializeController(_sut);
}
[TestMethod]
public void HomeController_Index_Ajax()
{
// arrange
_sut.Request.Stub(x => x["X-Requested-With"]).Return("XMLHttpRequest");
// act
var actual = _sut.Index();
// assert
// TODO: ...
}
[TestMethod]
public void HomeController_Index_Non_Ajax()
{
// act
var actual = _sut.Index();
// assert
// TODO: ...
}
}
Much prettier. It also allows you to write much more expressive asserts on the action results. Checkout the doc or ask if for more info is needed.
Expect.Call(_myController.Request.Headers["X-Requested-With"]).Return("SpitAndDuctTape").Repeat.Any();
...should do the job.