I am currently running unit tests on a controller that utilizes Url.Action to get the absolute path of two different routes. I am using Moq to set up the mock Action. The test is currently failing with the message 'Object reference not set to an instance of an object.'
[Edit - Clarifying the question]
When I debug the test, the Url shows to as a Mock<IUrlHelper> with an ActionContext property, but on the line where it calls the Url.Action, the Action property shows as null. What am I missing when setting up the Mock<IUrlHelper> that way the Url.Action doesn't come back as null?
I've checked multiple options for setting up Mock Url Actions, but nothing has seemed to work for me.
Here is the setup for the test
[Fact]
public async void SendGroup_PassesDetailsToNotificationService()
{
UrlActionContext actualContext = null;
var criteria = new NotificationCriteria { Section = 1, Subsection = 2 };
userSvc.GetNotificationGroupReturnValue = new List<NotificationRecipient>
{
new NotificationRecipient{ SmsAuth = true }
};
var actionContext = new ActionContext
{
ActionDescriptor = new ActionDescriptor(),
RouteData = new RouteData(),
};
var urlHelper = new Mock<IUrlHelper>();
urlHelper.SetupGet(h => h.ActionContext).Returns(actionContext);
urlHelper.Setup(h => h.Action(It.IsAny<UrlActionContext>()))
.Callback((UrlActionContext context) => actualContext = context);
controller.Url = urlHelper.Object;
var dummy = await controller.SendGroup(criteria);
Assert.Same(criteria, notificationSvc.SendGroupNotificationDetailUsed);
}
This is the code for the controller
[HttpPost]
public async Task<IActionResult> SendGroup([FromBody]NotificationCriteria criteria)
{
List<NotificationRecipient> recipients = (await userSvc.GetNotificationGroup(
criteria.Section, criteria.Subsection)).ToList();
if (!recipients.Any())
{
return BadRequest();
}
var uro = Url;
var sectionUrl = Url.Action("Index", "ReportHome", new {sectionId = criteria.Section}, Request.Scheme);
var profileUrl = Url.Action("Index", "Profile", null, Request.Scheme);
var detail = new NotificationDetail
{
SectionUrl = sectionUrl,
ProfileUrl = profileUrl,
Recipients = recipients
};
try
{
await notificationSvc.SendGroupNotification(detail);
}
catch
{
return StatusCode(500);
}
return Ok();
}
Related
In my Asp.net core 1 app I have controller with following method:
[Microsoft.AspNetCore.Mvc.HttpPost()]
[Microsoft.AspNetCore.Mvc.RequireHttps]
public async System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> Save()
{
if (ModelState.IsValid)
{
try
{
var pass = Request.Form["password"].ToString();
var pass1 = Request.Form["password1"].ToString();
if (!pass.Equals(pass1))
{
return View("~/Views/PasswordRecovery.cshtml");
}
}
catch (System.Exception ex)
{
return View("~/Views/Message.cshtml");
}
}
return View("~/Views/Message.cshtml");
}
I want to write a test for this method. So I have written this:
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Controllers.PasswordRecoveryController(_mockRepo.Object);
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
var collection = new Microsoft.AspNetCore.Http.FormCollection(dic);
controller.Request.Form = collection; //request is null
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/Message.cshtml", viewResult.ViewName);
}
The problem is that I need to set some test values to Form, Form is in Request, and Request is NULL. I can not find, how can I create some not NULL request and fill it's Form with values.
EDIT
Answers helped me to finish up with following solution:
I've created a method that will return a FormCollection:
private Microsoft.AspNetCore.Http.FormCollection GetFormCollection()
{
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
return new Microsoft.AspNetCore.Http.FormCollection(dic);
}
And my test method is:
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Findufix.Controllers.PasswordRecoveryController(_mockRepo.Object);
var httpContext = new Moq.Mock<Microsoft.AspNetCore.Http.HttpContext>();
httpContext.Setup( x => x.Request.Form).Returns(GetFormCollection());
controller.ControllerContext.HttpContext = httpContext.Object;
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/PasswordRecovery.cshtml", viewResult.ViewName);
}
If you pass a DefaultHttpContext to your controller, Request won't be null and you can assign the form to Request.Form. No mocking required.
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Controllers.PasswordRecoveryController(_mockRepo.Object);
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
var collection = new Microsoft.AspNetCore.Http.FormCollection(dic);
// Give the controller an HttpContext.
controller.ControllerContext.HttpContext = new DefaultHttpContext();
// Request is not null anymore.
controller.Request.Form = collection;
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/Message.cshtml", viewResult.ViewName);
}
With Moq, you can do it like this:
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(c => c.Request.Form).Returns(delegate()
{
var formVaues = new NameValueCollection();
formVaues .Add("Id", "123");
formVaues .Add("Name", "Smith");
return formVaues ;
});
In Moq you can try to use Setup() or SetupGet() to teach it to return something that you need
something along the lines of
controller.SetupGet(x => x.Request.Form).Returns(collection);
I have a working application for which I need to now add a full set of unit tests. The current code stores the user information as follows:
HttpContext.Current.Session["UserInfo"] = userData;
I'm using moq for my testing, and my unit test has the following code:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(
context.Object,
new RouteData(),
controller);
JsonResult result = controller.LoginUser(
new LoginHelper {
userName = "myusername",
password = "invalidpassword"
}
) as JsonResult;
Of course when I get to where the login process tries to create the session data, I get an "Object reference not set to an instance of an object." error becuase HttpContext.Current is null.
Some research has shown me that using Current isn't exactly compatible with MSTest, so I understand that I might need to change the way I store/load my user information. However, I would like some advice on which way to go here.
I would appreciate any suggestions on either how to get my unit tests to work, or a different method to store the user info to make it more compatible with unit tests.
This was the solution I came up with. It is a combination of a few things I found in various places including here:
First, I created 2 classes .. MockHttpSession, and MockHelpers
public class MockHttpSession : HttpSessionStateBase
{
Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return m_SessionStorage[name]; }
set { m_SessionStorage[name] = value; }
}
public override void Abandon()
{
// Do nothing
}
}
public class MockHelpers
{
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://localhost/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer(
"id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
return httpContext;
}
}
Then I changed my unit test code to use these:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var session = new MockHttpSession();
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
context.SetupGet(x => x.Session).Returns(session);
HttpContext.Current = MockHelpers.FakeHttpContext();
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
JsonResult result = controller.LoginUser(new LoginHelper { userName = "MyUserName", password = ""InvalidPassword }) as JsonResult;
This was able to successfully test the code with both valid and invalid passwords.
Thanks everyone for your help
I'm new to using Moq. I am trying to get this unit test to work but my object seems to keep returning null. I saw online that the Setup() must match the actual call. I'm obviously not getting it cause it still doesn't work; it seems to match to me. Here's my code samples.
Test method from test project:
[TestMethod]
public void CanPutEmailOptOut()
{
var mockParticipant = new PscuParticipant
{
ParticipantId = 1,
DoNotSendCuRewardsEmails = false,
DoNotSendEarnBonusPointEmail = false,
CardNumber = "VPZS5zXFUex2SJikkXFVrnvt2/R38yomFXwkslgXNKkgAFsjvt94p1h6J/XUEc6yQ5JzmT6+W8AdxuBSbp9e0SXAN60oHuZtWhAgGHhU+GaxJfCQHitc2+VBSZ/DxwW7Bpw="
};
MockBootstrapper.Instance.WithRepositoryData(new[] {mockParticipant});
var input = new EmailOptOutContract
{
DoNotSendCuRewardsEmails = true,
DoNotSendEarnBonusPointEmail = true
};
_affinityOptOutApiClient
.Setup(
x =>
x.CallAffinityOptOutStatus(It.IsAny<string>(),
It.IsAny<string>(),
mockParticipant.DoNotSendEarnBonusPointEmail,
mockParticipant.ParticipantId))
.Returns<HindaHttpResponse<OptOutResponse>>(x => new HindaHttpResponse<OptOutResponse>
{
StatusCode = AffinityResultCode.Success,
ResponseObject = new OptOutResponse { MemberId = "999999999", Status = "success" }
});
var response = Controller.Put(mockParticipant.ParticipantId, input);
var contract = response.ShouldBeSuccess<SuccessContract>();
var participant = RepositoryFactory.CreateReadOnly<PscuParticipant>().FirstOrDefault(x => x.ParticipantId == mockParticipant.ParticipantId);
Assert.AreEqual(input.DoNotSendCuRewardsEmails, participant.DoNotSendCuRewardsEmails);
Assert.AreEqual(input.DoNotSendEarnBonusPointEmail, participant.DoNotSendEarnBonusPointEmail);
}
protected override void Configure()
{
MockBootstrapper.Override(config => config.For<IEncryptionService>().Use<EncryptionService>());
_affinityOptOutApiClient = new Mock<IAffinityOptOutApiClient>(MockBehavior.Strict);
MockBootstrapper.Override(config => config.For<IAffinityOptOutApiClient>().Use(_affinityOptOutApiClient.Object));
}
Here's the method from my controller:
public HttpResponseMessage Put(int participantId, [FromBody]EmailOptOutContract contract)
{
if (contract == null)
return Failure(ApiReturnCodes.InvalidRequestContract
, "Invalid Request Contract",
string.Format("Contract Is Null in controller method {0}", System.Reflection.MethodBase.GetCurrentMethod()),
HttpStatusCode.BadRequest);
using (new UnitOfWorkScope())
{
var participant = GetParticipant(participantId);
if (participant == null)
{
return NotFound(ApiReturnCodes.ParticipantNotFound, "Participant ID not found.");
}
participant.DoNotSendCuRewardsEmails = contract.DoNotSendCuRewardsEmails;
participant.DoNotSendEarnBonusPointEmail = contract.DoNotSendEarnBonusPointEmail;
string cardNumber = ServiceLocator.Current.GetInstance<IEncryptionService>().Decrypt(participant.CardNumber);
cardNumber = AesEncrypt(cardNumber);
string email = null;
var partic = GetParticipantData(participant.ParticipantId);
if (partic != null)
email = partic.Email;
HindaHttpResponse<OptOutResponse> response =
_affinityOptOutApiClient.CallAffinityOptOutStatus(cardNumber, email, contract.DoNotSendEarnBonusPointEmail, participant.ParticipantId);
if (response.StatusCode == AffinityResultCode.Success && response.ResponseObject.Status == "success")
participant.AffinityMembId = response.ResponseObject.MemberId;
else
return BadRequest(ApiReturnCodes.AffinityInternalServerError, response.ExternalErrorMessage);
return Ok();
}
}
The part that comes back null in the controller is
HindaHttpResponse<OptOutResponse> response =
_affinityOptOutApiClient.CallAffinityOptOutStatus(cardNumber, email, contract.DoNotSendEarnBonusPointEmail, participant.ParticipantId);
The response object is null so when it is checked in the next statement for success, the exception is thrown. Does anyone know what might be wrong with my Setup/Return that's causing problems?
Thanks!!!!
In your controller you're changing participant.DoNotSendCuRewardsEmails to the value in the contract object, which in your setup of that is false. You setup your method to expect true for that parameter as that is the value contained in participant when setup is called. Moq gets the value of the property as is when setup is called, it doesn't lazy evaluate the objects property.
When you setup the mock, you have to use input
x.CallAffinityOptOutStatus(
It.IsAny<string>(),
It.IsAny<string>(),
input.DoNotSendEarnBonusPointEmail,
mockParticipant.ParticipantId)
It needs to match the specific call you are making inside the controller.
For my unit-tests I use Microsoft.VisualStudio.TestTools.UnitTesting and MvcContrib.TestHelper
my action in controller:
public ActionResult index()
{
try
{
Session.Add("username", "Simon");
var lSessionID = Session.SessionID;
return Content(lSessionID);
}
catch
{
}
return Content("false");
}
my unit-test:
[TestMethod]
public void IndexTestMethod1()
{
TestControllerBuilder builder = new TestControllerBuilder();
StartController controller = new StartController();
builder.InitializeController(controller);
var lResult = controller.index();
var lReturn = ((System.Web.Mvc.ContentResult)(lResult)).Content; // returns "false"
Assert.IsFalse(lReturn == "false");
}
When I call the index()-action in my browser, it shows the Session-ID. When I call the action via my unit-test, the lReturn is "false" and not the expected Session-ID.
How can I get the Session.SessionID in my unit-test?
The Session variable is read from ControllerContext.HttpContext.Session , and Session is of the type HttpSessionStateBase.
with in the unit test, you can use set the ControllerContext object. ( or use any mock providers like moq). I have not tested the code
var contextMock = new Mock<ControllerContext>();
var mockHttpContext = new Mock<HttpContextBase>();
var session = new Mock<HttpSessionStateBase>();
mockHttpContext.Setup(h => h.Session).Returns(session.Object);
contextMock.Setup(c => c.HttpContext).Returns(mockHttpContext.Object);
Current Solution
So I have something very similar to
[HttpPost]
public ActionResult Upload()
{
var length = Request.ContentLength;
var bytes = new byte[length];
if (Request.Files != null )
{
if (Request.Files.Count > 0)
{
var successJson1 = new {success = true};
return Json(successJson1, "text/html");
}
}
...
return Json(successJson2,"text/html");
}
Unit testable solution?
I want something like this:
[HttpPost]
public ActionResult Upload(HttpRequestBase request)
{
var length = request.ContentLength;
var bytes = new byte[length];
if (request.Files != null )
{
if (request.Files.Count > 0)
{
var successJson1 = new {success = true};
return Json(successJson1);
}
}
return Json(failJson1);
}
However this fails, which is annoying as I could make a Mock from the base class and use it.
Notes
I am aware this is not a good way to parse a form/upload and would
like to say other things are going on here (namely that this upload
can be a form or an xmlhttprequest - the action does not know which).
Other ways to make "Request" unit testable would also be awesome.
You already have a Request property on your controller => you don't need to pass it as action argument.
[HttpPost]
public ActionResult Upload()
{
var length = Request.ContentLength;
var bytes = new byte[length];
if (Request.Files != null)
{
if (Request.Files.Count > 0)
{
var successJson1 = new { success = true };
return Json(successJson1);
}
}
return Json(failJson1);
}
Now you can mock the Request in your unit test and more specifically the HttpContext which has a Request property:
// arrange
var sut = new SomeController();
HttpContextBase httpContextMock = ... mock the HttpContext and more specifically the Request property which is used in the controller action
ControllerContext controllerContext = new ControllerContext(httpContextMock, new RouteData(), sut);
sut.ControllerContext = controllerContext;
// act
var actual = sut.Upload();
// assert
... assert that actual is JsonResult and that it contains the expected Data