Has anyone mocked FriendlyUrls for unit testing?
I am writing a test that needs to mock asp.net FriendlyUrls. The call I need to mock specifically is Request.GetFriendlyUrlSegments(). I am using MS Fakes.
Here is my test so far:
// Arrange
var httpContext = TestHelper.StubHtppContext("", "http://localhost/content.aspx/area/controller/action/OtherRouteValue", "");
var httpContextBase = new HttpContextWrapper(httpContext);
RouteTable.Routes.MapRoute(
"RouteName",
"Area/{controller}/{action}/{id}/{OtherRoute}",
new {action = "Index", id = UrlParameter.Optional, OtherRoute = UrlParameter.Optional});
RouteTable.Routes.EnableFriendlyUrls();
var segments = new List<String> {"Controller", "Action", "Id", "OtherRoute"};
using (ShimsContext.Create())
{
ShimHttpContext.CurrentGet = () => httpContext;
ShimFriendlyUrl.SegmentsGet = () => segments;
// Act
RouteData result = MvcUtility.GetRouteValuesFromUrl();
// Assert
Assert.IsNotNull(result, "Expected RouteData to be created.");
}
}
The relevant Part of the system under test:
public static RouteData GetRouteValuesFromUrl()
{
var request = System.Web.HttpContext.Current.Request;
var segments = request.GetFriendlyUrlSegments();
//Other code
}
I would expect for segments to use my shim get and return my list of segments.
My code works when I run it in the web context, I just need to find a way to unit test it and the first step is mocking shim/stub this request.GetFriendlyUrlSegments() call.
Based on slaks suggestion, the right data to fake here is the Request.RequestContext.RouteData. It needs a DataToken with a "FriendlyUrlSegments" key.
The corrected and working test:
var httpContext = TestHelper.StubHtppContext("", "http://localhost/content.aspx/area/controller/action/OtherRouteValue", "");
var httpContextBase = new HttpContextWrapper(httpContext);
RouteTable.Routes.MapRoute(
"RouteName",
"Area/{controller}/{action}/{id}/{OtherRoute}",
new {action = "Index", id = UrlParameter.Optional, OtherRoute = UrlParameter.Optional});
RouteTable.Routes.EnableFriendlyUrls();
var segments = new List<String> {"Controller", "Action", "Id", "OtherRoute"};
var requestContext = new StubRequestContext(httpContextBase, new RouteData());
var mockedRouteData = new RouteData();
mockedRouteData.DataTokens.Add("FriendlyUrlSegments", segments);
requestContext.RouteDataGet = () => mockedRouteData;
using (ShimsContext.Create())
{
ShimHttpContext.CurrentGet = () => httpContext;
ShimHttpRequest.AllInstances.RequestContextGet = request => requestContext;
// Act
RouteData result = MvcUtility.GetRouteValuesFromUrl();
// Assert
Assert.IsNotNull(result, "Expected RouteData to be created.");
Related
I have a asp.net web app that calls a controller action with the following code:
$(function () {
$("#barcode").on("change", function (e) {
// get the current value
var barcode = $('#barcode').val();
// if there's no text, ignore the event
if (!barcode) {
return;
}
// clear the textbox
$("#barcode").val("");
// var holdit = $('#textArea1').val();
$('#textArea1').val($('#textArea1').val() +' '+ barcode);
// post the data using AJAX
$.post('#Url.Action("scanned", "receiveScan")?barcode=' + barcode);
});
})
Controller:
[Produces("application/json")]
[Route("api/receiveScan")]
public class receiveScanController : Controller
{
private static readonly HttpClient client = new HttpClient();
public ActionResult scanned(string barcode)
{
var test = barcode;
receiveScanModel newScan = new receiveScanModel();
newScan.Barcode = barcode;
newScan.companyNo = 1;
string jsonScan = JsonConvert.SerializeObject(newScan, Formatting.Indented);
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://notarealservicehere.azurewebsites.net//api/receivescan");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
streamWriter.Write(jsonScan);
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
return Ok();
Trying to convert to my first Razor page, everything works with the exception (obviously) of the $.post part...
Where would this go?
It is a asp.net core app with razor pages
Use a handler for example
on your cs file
public IActionResult OnPostBarcode(string barcode)
on your js
var uri = "myPage/?handler=Barcode"
$.post( uri ,{barcode:barcode}, function( data ) {
console.log(data)
});
I'm trying to output Json, with Json.net, by building up a dynamic then calling JsonConvert.Serialize().
The problem is that my _camelCase contract resolver is not respected, and the output is not camel case.
private JsonSerializerSettings _camelCase = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var myObj = new { Prop = "val"};
var myObj2 = new { Prop = "val" };
var objOut = JsonConvert.SerializeObject(myObj, _camelCase);
// {"prop":"val"}
// No problem
// now, a list
var list = new List<object> { myObj, myObj2 };
var listOut = JsonConvert.SerializeObject(list, _camelCase);
// [{"prop":"val"},{"prop":"val"}]
// no problem
// now, put my list in a dynamic
dynamic myDynamic = new JObject();
myDynamic.List = JArray.FromObject(list);
var dynamicOut = JsonConvert.SerializeObject(myDynamic, _camelCase);
// {"List":[{"Prop":"val"},{"Prop":"val"}]}
// PROBLEM! _camelCase is not respected
Can anyone tell me what is going on here?
Ok in case this can be useful for anyone... The serializer settings that count are not those supplied to JsonConvert.Serialize(), but those used for the call JArray.FromObject(). So I needed to create a serializer with the contract resolver...
private JsonSerializerSettings _camelCase = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
private JsonSerializer _camelCaseSerializer = JsonSerializer.Create(_camelCase);
...then use it for FromObject()...
myDynamic.List = JArray.FromObject(list, _camelCaseSerializer );
I could also have changed the default settings like this...
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
... but I lack the courage :-)
I'm trying to write a middleware for batch requests i .net core 2.0.
So far the I have splitted the request, pipe each request on to the controllers.
The controllers return value, but for some reason the response on the created context that I parse to the controllers keeps giving me a NullStream in the body, so I think that there is something that I miss in my setup.
The code looks like this:
var json = await streamHelper.StreamToJson(context.Request.Body);
var requests = JsonConvert.DeserializeObject<IEnumerable<RequestModel>>(json);
var responseBody = new List<ResponseModel>();
foreach (var request in requests)
{
var newRequest = new HttpRequestFeature
{
Body = request.Body != null ? new MemoryStream(Encoding.ASCII.GetBytes(request.Body)) : null,
Headers = context.Request.Headers,
Method = request.Method,
Path = request.RelativeUrl,
PathBase = string.Empty,
Protocol = context.Request.Protocol,
Scheme = context.Request.Scheme,
QueryString = context.Request.QueryString.Value
};
var newRespone = new HttpResponseFeature();
var requestLifetimeFeature = new HttpRequestLifetimeFeature();
var features = CreateDefaultFeatures(context.Features);
features.Set<IHttpRequestFeature>(newRequest);
features.Set<IHttpResponseFeature>(newRespone);
features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
var innerContext = _factory.Create(features);
await _next(innerContext);
var responseJson = await streamHelper.StreamToJson(innerContext.Response.Body);
I'm not sure what it is I'm missing in the setup, since innerContext.Response.Body isn't set.
One of the endpoints that I use for testing and that gets hit looks like this
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I found the error, or two errors for it to work.
First I had to change my newResponse to
var newRespone = new HttpResponseFeature{ Body = new MemoryStream() };
Since HttpResponseFeature sets Body to Stream.Null in the constructor.
When that was done, then Body kept giving an empty string when trying to read it. That was fixed by setting the Position to Zero like
innerContext.Response.Body.Position = 0;
I've never had to do this before, because it's always only been an actual form that I've posted as that content type, but recently I had to post three variables like that, and I resorted to a sordid concatenation with & and =:
var content = new StringContent("grant_type=password&username=" + username + "&password=" + password.ToClearString(), Encoding.UTF8,
"application/x-www-form-urlencoded");
I'm sure there must be a utility method that would do that, and do it better, with any necessary encoding. What would that be?
If this is a POCO and just using the Newtonsoft library, you can use this as well:
public static class FormUrlEncodedContentExtension
{
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var keyValues = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var content = new FormUrlEncodedContent(keyValues);
return content;
}
}
And a sample usage would be:
var myObject = new MyObject {Grant_Type = "TypeA", Username = "Hello", Password = "World"};
var request = new HttpRequestMessage(HttpMethod.Post, "/path/to/post/to")
{
Content = myObject.ToFormUrlEncodedContent()
};
var client = new HttpClient {BaseAddress = new Uri("http://www.mywebsite.com")};
var response = await client.SendAsync(request);
Use reflection to get the property names and values and then use them to create a System.Net.Http.FormUrlEncodedContent
public static class FormUrlEncodedContentExtension {
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj) {
var nameValueCollection = obj.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => (p.GetValue(obj) ?? "").ToString());
var content = new FormUrlEncodedContent(nameValueCollection);
return content;
}
}
From there it is a simple matter of calling the extension method on an object to convert it to a FormUrlEncodedContent
var model = new MyModel {
grant_type = "...",
username = "...",
password = "..."
};
var content = model.ToFormUrlEncodedContent();
You should be able to use string interpolation for that. Something like:
var content = new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
Or wrap this inside a helper/factory method:
public static class StringContentFactory
{
public static StringContent Build(string username, string password)
{
return new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
}
}
I’m trying to fake http context to test a Controller. My environment is MVC 3 and Moq 4.
So far I have tried a few options including:
a.
var searchController = new MySearchController(_mockResolver.Object.Resolve<IConfiguration>());
var mockContext = new Mock<ControllerContext>();
searchController.ControllerContext = mockContext.Object;
var result = searchController.Render();
b.
var searchController = new MopSearchController(_mockResolver.Object.Resolve<IConfiguration>());
searchController.MockControllerContext();
var result = searchController.Render();
public static class MockHttpHelper
{
public static Mock<HttpContextBase> MockControllerContext(
this Controller controller, string path = null)
{
var mockHttpCtx = MockHttpHelper.MockHttpContext(path);
var requestCtx = new RequestContext(mockHttpCtx.Object, new RouteData());
var controllerCtx = new ControllerContext(requestCtx, controller);
controller.ControllerContext = controllerCtx;
return mockHttpCtx;
}
public static Mock<HttpContextBase> MockHttpContext(string path)
{
var mockHttpCtx = new Mock<HttpContextBase>();
var mockReq = new Mock<HttpRequestBase>();
mockReq.SetupGet(x => x.RequestType).Returns("GET");
mockReq.SetupGet(req => req.Form).Returns(new NameValueCollection());
mockReq.SetupGet(req => req.QueryString).Returns(new NameValueCollection());
mockHttpCtx.SetupGet(x => x.Request).Returns(mockReq.Object);
return mockHttpCtx;
}
}
Neither of these work, I get the exception below. Can anyone point me in the direction of a working example? I’ve seen quite a few questions on the net around the same topic, but given the date (posts from 2008-2010) and MVC version (i.e. 1 and 2) I feel like I’m missing something / or trying to mock more than I need to in MVC3.
System.NullReferenceException : Object reference not set to an instance of an object.
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.<>c__DisplayClassc.<GetValueProvider>b__7(ValueProviderFactory factory)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList(IEnumerable`1 source)
at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.Controller.TryUpdateModel(TModel model)
Thanks
Yes, all you were really missing, as you've noted, was setting the Controller's ValueProvider. Even though you're using this controller with a Get action but no Post action, the Controller still gets its ValueProvider instantiated upon creation, so you need to do the same thing in your test scenario. Here's the base class that I use when testing my controllers. I use NBehave's NUnit wrapper for unit testing, so ignore the SpecBase reference if you wish
public abstract class MvcSpecBase<T> : SpecBase<T> where T : Controller
{
protected T Controller { get; set; }
protected string RelativePath = string.Empty;
protected string AbsolutePath = string.Empty;
protected void InitialiseController(T controller, NameValueCollection collection, params string[] routePaths)
{
Controller = controller;
var routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
var httpContext = ContextHelper.FakeHttpContext(RelativePath, AbsolutePath, routePaths);
var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), Controller);
var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData()), routes);
Controller.ControllerContext = context;
Controller.ValueProvider = new NameValueCollectionValueProvider(collection, CultureInfo.CurrentCulture);
Controller.Url = urlHelper;
}
}
Then, in your test, create your controller and then call this line:
InitialiseController(controller, new FormCollection());