Can't get IValueProvider values from QueryStringValueProvider(ControllerContext) in Unit Testing? - c#

Here is the Test method Creating a mock for request and context
have added the querystring to the context and while debugging the TestMethod could able to see the values of the querystring collection
[TestMethod]
public void Save_Tester()
{
//Arrange
HomeController controller = new HomeController();
string querystring = "?key1=value1&key2=value2&key3=value3&key4=value4";
Mock<HttpRequestBase> mock_request = MockHelpers.CreateMockRequest(querystring);
Mock<HttpContextBase> mock_context = new Mock<HttpContextBase>();
//Request
NameValueCollection myValues = new NameValueCollection();
FormCollection formcollection = new FormCollection(myValues);
mock_request.SetupGet(mr => mr.Params).Returns(myValues);
mock_request.SetupGet(mr => mr.Form).Returns(myValues);
mock_request.SetupGet(mr => mr.QueryString).Returns(HttpUtility.ParseQueryString(querystring));
//Context
mock_context.Setup(c => c.Request).Returns(mock_request.Object);
controller.ValueProvider = formcollection.ToValueProvider();
// Act
Assert.IsNotNull(controller); //Guard
var result_query = controller.Save() as ViewResult;
// Assert
}
In the save method using QueryStringValueProvider to get the Values but has no namevaluecollection
QueryStringValueProvider takes the ControllerContext.HttpContext.Request.QueryString that to avail in debugging
[HttpPost]
public ActionResult Save()
{
try
{
IValueProvider provider = new QueryStringValueProvider(this.ControllerContext);
//provider has no namevaluecollection values
}
catch
{
throw;
}
}

Solution
This solved it I should have taken some time to analyse before putting a question
Uri uri = new Uri("http://localhost?key1=value1&key2=value2&key3=value3&&key4=value4");
HttpRequest httpRequest = new HttpRequest(string.Empty, uri.ToString(),
uri.Query.TrimStart('?'));
HttpContext httpContext = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
HttpSessionStateContainer sessionContainer = new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10, true, HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
SessionStateUtility.AddHttpSessionStateToContext(
httpContext, sessionContainer);
HttpContext.Current = httpContext;

Related

Mock the Request when testing a Razor Page

I need to create a unit test for this function that resides inside the HomeModel razor page
public async Task<IActionResult> OnGetCurrencyAsync(string currency, CancellationToken ct = default)
{
var returnUrl = Request.Headers["Referer"].ToString();
var path = new System.Uri(returnUrl).LocalPath;
if (string.IsNullOrWhiteSpace(path) || !Url.IsLocalUrl(path))
returnUrl = Url.Content("~/");
var session = await _currentUserService.GetOrInitializeSessionAsync(ct);
if (!currency.IsNullOrEmpty())
{
session.Currency = currency;
await _currentUserService.SetSession(session, ct);
}
return Redirect(returnUrl);
}
Till now I've created the following test
[Fact]
public async Task Test1()
{
var returnUrl = "https://localhost:44317/paris";
var currentuserService = new Mock<ICurrentUserService>();
var options = new Mock<IOptions<Core.Configuration.AppSettings>>();
var navigationMenu = new Mock<INavigationMenu>();
var productModelService = new Mock<IProductModelService>();
var userSavedProductRepository = new Mock<IUserSavedProductRepository>();
var userSavedProductService = new Mock<IUserSavedProductService>();
var homePage = new HomeModel(currentuserService.Object, options.Object, navigationMenu.Object, productModelService.Object, userSavedProductService.Object, userSavedProductRepository.Object);
var res = await homePage.OnGetCurrencyAsync("EUR", CancellationToken.None);
Assert.IsType<RedirectResult>(res);
var redirectResult = (RedirectResult)res;
Assert.True(returnUrl == redirectResult.Url);
}
But when I execute it, I got that .Request is null..
How can I correctly set it up?
The PageContext of the subject PageModel needs a HttpContext that contains the desired Request setup to satisfy the subject under test.
Reference: Razor Pages unit tests in ASP.NET Core: Unit tests of the page model methods
//Arrange
var returnUrl = "https://localhost:44317/paris";
//...code omitted for brevity
// use a default context to have access to a request
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Referer"] = returnUrl; //<--
//these are needed as well for the page context
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
// need page context for the page model
var pageContext = new PageContext(actionContext) {
ViewData = viewData
};
//create model with necessary dependencies applied
var homePage = new HomeModel(currentuserService.Object, options.Object, navigationMenu.Object, productModelService.Object, userSavedProductService.Object, userSavedProductRepository.Object) {
PageContext = pageContext, //<--
Url = new UrlHelper(actionContext)
};
//Act
//...omitted for brevity

Path to RouteValueDictionary in Asp.Net Core

I need to extract the route data (Controller, Action etc) from an arbitrary request path (not related to the current request) such as / or /account/manage. In previous versions of Asp.Net Mvc this could be accomplished like this:
var request = new HttpRequest(null, "http://localhost:3333/Home/About", "testvalue=1");
var response = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var values = routeData.Values;
// The following should be true for initial version of mvc app.
values["controller"] == "Home"
values["action"] == "Index"
Source
This solution is not optimal since it requires a fully qualified Url instead of just a request path.
You can use TemplateMatcher to extract route values:
public class RouteMatcher
{
public static RouteValueDictionary Match(string routeTemplate, string requestPath)
{
var template = TemplateParser.Parse(routeTemplate);
var matcher = new TemplateMatcher(template, GetDefaults(template));
var values = new RouteValueDictionary();
var moduleMatch = matcher.TryMatch(requestPath, values);
return values;
}
// This method extracts the default argument values from the template.
private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
{
var result = new RouteValueDictionary();
foreach (var parameter in parsedTemplate.Parameters)
{
if (parameter.DefaultValue != null)
{
result.Add(parameter.Name, parameter.DefaultValue);
}
}
return result;
}
}
And example usage:
var template = "{controller=Home}/{action=Index}/{id?}";
var routeValues = RouteMatcher.Match(template, "<your path>");
See this article: https://blog.markvincze.com/matching-route-templates-manually-in-asp-net-core/

Correct mock for Http/Controller context for a razor helper test unit

I have an issue trying to create a unit test for testing a custom razor helper (that using the kendo grid razor helper).
The problems comes from the kendo grid helper, which try to access the following property : HtmlHelper.ViewContext.Controller.ValueProvider (and throws a NullReferenceException. This exception comes from the access to the ValueProvider property of the controller context).
I concluded that my "mock" http/controller context is not correctly initialized. I try several solutions found on the internet for mocking the context, but I still have the problem).
This is my current unit test code:
ModelMock model = ...
ViewDataDictionary vd = new ViewDataDictionary(model);
var htmlHelper = CreateHtmlHelper<ModelMock>(vd);
string htmlResult = htmlHelper.PopinGrid(m => m.JsonList).ToHtmlString(); // This is the call of my helper.
... (asserts)
And my helpers for context mocking (they work with other simple unit tests for razor helpers):
public static HtmlHelper<T> CreateHtmlHelper<T>(ViewDataDictionary viewDataDictionary)
where T : new()
{
Mock<ControllerBase> controller = new Mock<ControllerBase>();
Mock<ControllerContext> controllerContext = new Mock<ControllerContext>(
new Mock<HttpContextBase>().Object,
new RouteData(),
controller.Object);
Mock<ViewContext> viewContext = new Mock<ViewContext>(
controllerContext.Object,
new Mock<IView>().Object,
viewDataDictionary,
new TempDataDictionary(),
new StringWriter(CultureInfo.InvariantCulture));
Mock<IViewDataContainer> mockViewDataContainer = new Mock<IViewDataContainer>();
bool unobtrusiveJavascriptEnabled = false;
bool clientValidationEnabled = true;
viewContext.SetupGet(c => c.UnobtrusiveJavaScriptEnabled).Returns(unobtrusiveJavascriptEnabled);
viewContext.SetupGet(c => c.FormContext).Returns(new FormContext { FormId = "myForm" });
viewContext.SetupGet(c => c.ClientValidationEnabled).Returns(clientValidationEnabled);
viewContext.SetupGet(c => c.ViewData).Returns(viewDataDictionary);
// I add the following line because the "Controller" property of the viewContext was null (strange, given that I initialize the view context with the controller context).
viewContext.SetupGet(c => c.Controller).Returns(controller.Object);
mockViewDataContainer.Setup(v => v.ViewData).Returns(viewDataDictionary);
HttpContext.Current = FakeHttpContext();
return new HtmlHelper<T>(viewContext.Object, mockViewDataContainer.Object);
}
public static HttpContext FakeHttpContext()
{
HttpRequest httpRequest = new HttpRequest(string.Empty, "http://mockurl/", string.Empty);
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
HttpResponse httpResponse = new HttpResponse(stringWriter);
HttpContext httpContext = new HttpContext(httpRequest, httpResponse);
HttpSessionStateContainer sessionContainer = new HttpSessionStateContainer(
"id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
return httpContext;
}
Is anyone know the good solution for mocking a full http/controller context ?
Thanks !

How can I mock the Response.StatusCode with Moq?

I have the following method:
public void SetHttpStatusCode(HttpStatusCode httpStatusCode)
{
Response.StatusCode = (int)httpStatusCode;
}
And the following test:
[TestMethod]
public void SetHttpStatusCode_SetsCorrectStatusCode()
{
//Arrange
//Any url will suffice
var mockHttpContext = TestHelpers.MakeHttpContext("");
mockHttpContext.SetupSet(x => x.Response.StatusCode = It.IsAny<int>());
//creates an instance of an asp.net mvc controller
var controller = new AppController()
{
ControllerContext = new ControllerContext() {
HttpContext = mockHttpContext.Object }
};
// Act
controller.SetHttpStatusCode(HttpStatusCode.OK);
//Assert
mockHttpContext.VerifySet(x => x.Response.StatusCode = It.IsAny<int>());
}
Also, Here is MakeHttpContext
public static Mock<HttpContextBase> MakeHttpContext(string url)
{
var mockHttpContext = new Mock<HttpContextBase>();
var mockRequest = new Mock<HttpRequestBase>();
var mockResponse = new Mock<HttpResponseBase>();
var mockSession = new Mock<HttpSessionStateBase>();
//request
mockRequest.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);
mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
//response
mockResponse.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(x => x);
mockHttpContext.Setup(x => x.Response).Returns(mockResponse.Object);
//session
mockHttpContext.Setup(x => x.Session).Returns(mockSession.Object);
return mockHttpContext;
}
When I run the test, I get the following exception:
Test method PA.Tests.Controllers.AppControllerTest.SetHttpStatusCode_SetsCorrectStatusCode
threw exception:
Moq.MockException:
Expected invocation on the mock at least once,
but was never performed: x => x.StatusCode = It.IsAny<Int32>()
Configured setups:
x => x.StatusCode = It.IsAny<Int32>(), Times.Never
No invocations performed.
How does Moq expect/require invocations to be called? I've debugged the SetHTTPStatusCode method, the response object is indeed a mocked object, however Moq insists that there was no invocation. Am I missing something?
Thanks!
You haven't shown what your TestHelpers.MakeHttpContext method does so it's a bit difficult to understand what's going on.
Try like this:
// Arrange
var mockHttpContext = new Mock<HttpContextBase>();
var response = new Mock<HttpResponseBase>();
mockHttpContext.SetupGet(x => x.Response).Returns(response.Object);
//creates an instance of an asp.net mvc controller
var controller = new AppController()
{
ControllerContext = new ControllerContext()
{
HttpContext = mockHttpContext.Object
}
};
// Act
controller.SetHttpStatusCode(HttpStatusCode.OK);
//Assert
response.VerifySet(x => x.StatusCode = (int)HttpStatusCode.OK);

mock HttpContext.Current.Server.MapPath using Moq?

im unit testing my home controller. This test worked fine until I added a new feature which saves images.
The method that’s causing the issue is this below.
public static void SaveStarCarCAPImage(int capID)
{
byte[] capBinary = Motorpoint2011Data.RetrieveCapImageData(capID);
if (capBinary != null)
{
MemoryStream ioStream = new MemoryStream();
ioStream = new MemoryStream(capBinary);
// save the memory stream as an image
// Read in the data but do not close, before using the stream.
using (Stream originalBinaryDataStream = ioStream)
{
var path = HttpContext.Current.Server.MapPath("/StarVehiclesImages");
path = System.IO.Path.Combine(path, capID + ".jpg");
Image image = Image.FromStream(originalBinaryDataStream);
Image resize = image.GetThumbnailImage(500, 375, null, new IntPtr());
resize.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
As the call is coming from a unit test, HttpContext.Current is null and throws an exception. After reading about Moq and some of the tutorials about using Moq with sessions, im sure it can be done.
so far this the unit test code have come up with, but the issue is HTTPContext.Current is always null, and still throws the exception.
protected ControllerContext CreateStubControllerContext(Controller controller)
{
var httpContextStub = new Mock<HttpContextBase>
{
DefaultValue = DefaultValue.Mock
};
return new ControllerContext(httpContextStub.Object, new RouteData(), controller);
}
[TestMethod]
public void Index()
{
// Arrange
HomeController controller = new HomeController();
controller.SetFakeControllerContext();
var context = controller.HttpContext;
Mock.Get(context).Setup(s => s.Server.MapPath("/StarVehiclesImages")).Returns("My Path");
// Act
ViewResult result = controller.Index() as ViewResult;
// Assert
HomePageModel model = (HomePageModel)result.Model;
Assert.AreEqual("Welcome to ASP.NET MVC!", model.Message);
Assert.AreEqual(typeof(List<Vehicle>), model.VehicleMakes.GetType());
Assert.IsTrue(model.VehicleMakes.Exists(x => x.Make.Trim().Equals("Ford", StringComparison.OrdinalIgnoreCase)));
}
HttpContext.Current is something that you should absolutely never use if you ever expect your code to be unit tested. It is a static method which simply returns null if there is no web context which is the case of a unit test and cannot be mocked. So one way to refactor your code would be the following:
public static void SaveStarCarCAPImage(int capID, string path)
{
byte[] capBinary = Motorpoint2011Data.RetrieveCapImageData(capID, path);
if (capBinary != null)
{
MemoryStream ioStream = new MemoryStream();
ioStream = new MemoryStream(capBinary);
// save the memory stream as an image
// Read in the data but do not close, before using the stream.
using (Stream originalBinaryDataStream = ioStream)
{
path = System.IO.Path.Combine(path, capID + ".jpg");
Image image = Image.FromStream(originalBinaryDataStream);
Image resize = image.GetThumbnailImage(500, 375, null, new IntPtr());
resize.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
You see, now this method no longer depends on any web context and can be tested in isolation. It will be the responsibility of the caller to pass the correct path.
I agree with the Darin´s answer but if you really need to moq the Server.MapPath function you could do something like this
//...
var serverMock = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
serverMock.Setup(i => i.MapPath(It.IsAny<String>()))
.Returns((String a) => a.Replace("~/", #"C:\testserverdir\").Replace("/",#"\"));
//...
Performing this, the mock will simply replace the ~/ with the c:\testserverdir\ function
Hope it helps!
It is sometimes handy to just mock the call to server.MapPath.
This solution works for me using moq.
I only mock the base path to the application.
_contextMock = new Mock<HttpContextBase>();
_contextMock.Setup(x => x.Server.MapPath("~")).Returns(#"c:\yourPath\App");
_controller = new YourController();
_controller.ControllerContext = new ControllerContext(_contextMock.Object, new RouteData(), _controller);
In your controller you can now user Server.MapPath("~").
Below works for me.
string pathToTestScripts = #"..\..\..\RelatavePathToTestScripts\";
string testScriptsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pathToTestScripts);
var server = new Mock<HttpServerUtilityBase>(); // Need to mock Server.MapPath() and give location of random.ps1
server.Setup(x => x.MapPath(PowershellScripts.RANDOM_PATH)).Returns(testScriptsFolder + "random.ps1");
var request = new Mock<HttpRequestBase>(); // To mock a query param of s=1 (which will make random.ps1 run for 1 second)
request.Setup(x => x.QueryString).Returns(new System.Collections.Specialized.NameValueCollection { { "s", "1" } });
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(x => x.Server).Returns(server.Object);
httpContext.Setup(x => x.Request).Returns(request.Object);
YourController controller = new YourController();
controller.ControllerContext = new ControllerContext(httpContext.Object, new RouteData(), controller);

Categories

Resources