Mocking The RouteData Class in System.Web.Routing for MVC applications - c#

I'm trying to test some application logic that is dependent on the Values property in ControllerContext.RouteData.
So far I have
// Arrange
var httpContextMock = new Mock<HttpContextBase>(MockBehavior.Loose);
var controllerMock = new Mock<ControllerBase>(MockBehavior.Loose);
var routeDataMock = new Mock<RouteData>();
var wantedRouteValues = new Dictionary<string, string>();
wantedRouteValues.Add("key1", "value1");
var routeValues = new RouteValueDictionary(wantedRouteValues);
routeDataMock.SetupGet(r => r.Values).Returns(routeValues); <=== Fails here
var controllerContext = new ControllerContext(httpContextMock.Object, routeDataMock.Object, controllerMock.Object);
The unit test fails with:
System.ArgumentException: Invalid setup on a non-overridable member:
r => r.Values
Creating a fake RouteData doesn't work either as the constructor is RouteData(RouteBase,IRouteHandler).
The important class here is the abstract class RouteBase which has the method GetRouteData(HttpContextBase) which returns an instance of RouteData, the class I'm trying to fake. Taking me around in circles!
Any help on this would be most welcome.

RouteData also has a constructor that takes no arguments. Simply create one and add the values to it that you want. No need to mock it when you can create one.
var routeData = new RouteData();
routeData.Values.Add( "key1", "value1" );
var controllerContext = new ControllerContext(httpContextMock.Object, routeData, controllerMock.Object);

I'm very new to TDD in conjunction with mock objects, but a lesson I learned early on from a colleague was not to mock types you don't own. Thus, don't try to mock RouteData. The idea was originally conceived by Joe Walnes (though I can't find where he said it).

Related

How to moq HttpClientExtensions method "PostAsJsonAsync"?

I need to write the unit test case around PostAsJsonAsync, which is extension method in HttpClientExtensions. I don't find any easy way to moq it.
Can someone help me in this.
Two ways I can think of:
Use a framework like Moles: https://www.microsoft.com/en-us/research/project/moles-isolation-framework-for-net/ This way you can replace extensions or any other methods with your own, returning the value you want
This is my preferred way. In such cases, wrap the service inside a proxy Interface. You might find it called adapter pattern elsewhere but in my opinion you are just abstracting the actions and proxying the data.
So create the IHttpClientProxy and the corresponding concrete implementation that will use any extention you like. Pass the IHttpClientProxy to your class and mock it as you like.
As mentioned in the comments, there is no way the mocking frameworks to mock static methods this way. Frameworks like Moq, only mock virtual or abstract methods (interface methods are inherently abstract) to guide towards better design.
The issue for me was that I was not understanding that PostAsJsonAsync was actually a convenience method that ultimately calls SendAsync on the handler. So many of the answers you find relating to mocking HTTP client are accurate. The gist is that you are indeed mocking the HttpMessageHandler, and using a concrete HttpClient. The missing piece was that you still need to perform setup and verification on 'SendAsync', not PostAsJsonAsync. Here is the code that worked for me to test PostAsJsonAsync, because SendAsync gets called on the handler:
// Arrange
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':1,'value':'1'}]"),
})
.Verifiable();
// new up a real HttpClient, passing in the mocked handler
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://example.com")
};
// replace with an instance of your actual concrete client
var yourClient = new YourClientHere(httpClient);
// Act
// perform the action on yourClient that makes the PostAsJsonAsync call
// Assert
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(accessRights.Count),
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
);
This article helped me get everything set up correctly: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
I am using NSubstitute for mocking this extension method on HttpClient and it seems seems to work fine without the use of Moles or an adapter.
public class ApiClientTests
{
private HttpClient _client;
private string _url;
private ModelDto _testModel;
public void ApiClientTests()
{
_client = Substitute.For<HttpClient>();
_client.BaseAddress = new Uri("http://fakeUrl/api/")
_url = "Models/";
_testModel = new ModelDto
{
Id = 1,
Name = "Model Name",
Description = "Model Description",
Outputs = new Dictionary<string, ModelOutputDto>(),
Parameters = new Dictionary<string, ModelParamDto>(),
Active = true
};
}
[Fact]
public async Task CreateItemAsync_ValidResponseCode_ReturnsNewResourceUri()
{
// Arrange
var returnUri = $"{_client.BaseAddress}{_url}";
var returnThis = new HttpResponseMessage(System.Net.HttpStatusCode.Created);
returnThis.Headers.Location = new Uri(returnUri);
_client.PostAsJsonAsync(_url, _testModel).ReturnsForAnyArgs(Task.FromResult(returnThis));
var apiClient = new ApiClient<ModelDto>(_client);
// Act
var result = await apiClient.CreateItemAsync(_testModel, _url);
// Assert
await _client.ReceivedWithAnyArgs().PostAsJsonAsync(_url, _testModel);
result.AbsoluteUri.Should().BeEquivalentTo(returnUri);
}

Error trying to create Mock.Of<ControllerContext>() for ASP.Net Core 3.1 Unit Test

As per last section of the Moq Quickstart defined here, I am trying to configure the following Mock in order to pass Form values to the controller method under test:
var formCollection = new FormCollection(
new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>()
{
{"mAction", "someAction" },
{"mRefId", "0" }
});
var controllerContext = Mock.Of<ControllerContext>(ctx =>
ctx.HttpContext.Request.Form == formCollection);
controller.ControllerContext = controllerContext;
However, when the run the test, it fails on the Mock.Of<> line with the following error:
System.NotSupportedException : Unsupported expression: mock => mock.HttpContext
Non-overridable members (here: ActionContext.get_HttpContext) may not be used in setup / verification expressions.
What am I missing? Am I not doing it the same as per the example defined in the Quickstart document?
The error is because ControllerContext.HttpContext property is not virtual, so Moq is unable to override it.
Consider using an actual ControllerContext and mocking a HttpContext to assign to the property
var formCollection = new FormCollection(new Dictionary<string, StringValues>()
{
{"mAction", "someAction" },
{"mRefId", "0" }
});
var controllerContext = new ControllerContext() {
HttpContext = Mock.Of<HttpContext>(ctx => ctx.Request.Form == formCollection)
};
controller.ControllerContext = controllerContext;
//...
Or even using DefaultHttpContext and assign the desired value(s)
var formCollection = new FormCollection(new Dictionary<string, StringValues>()
{
{"mAction", "someAction" },
{"mRefId", "0" }
});
HttpContext httpContext = new DefaultHttpContext();
httpContext.Request.Form = formCollection;
var controllerContext = new ControllerContext() {
HttpContext = httpContext
};
controller.ControllerContext = controllerContext;
//...
You need to manually set all properties which are not marked virtual either by explicitly setting them in the lambda, or possibly setting them outside (there is no need to create a concrete object), this works by me at least in version 4.16.1.
Method 1 (when using Mock.Of()):
var mockCtx = Mock.Of<ControllerContext>(ctx =>
ctx.HttpContext == Mock.Of<HttpContext>(hCtx => hCtx.Request.Form
== formCollection))`
NOTE: In case you need to set all properties to be mocked, or do anything that needs Mock.Get() as in this answer you need to do it directly on the HttpContext as in Mock.Get(mockCtx.HttpContext ) and not on the mockCtx.
Method 2 (when using new Mock<>()):
var mockCtx = new Mock<ControllerContext>();
mockCtx.Object.HttpContext = Mock.Of<HttpContext>(hCtx => hCtx.Request.Form
== formCollection);
There is no need to create a concrete object, as method 2 is just doing the same but with a mock object.
Note: This will only work if the property is not readonly.

How to Mock Request object in unit testing asp.net mvc application

I am working on an asp.net mvc 3.0 application. In unit testing one of the action method in my controller, I was getting an error.
How to mock: Request.Params["FieldName"]
I have included Moq framework, but was not sure how to pass value
Here is my code... Please suggest...
var request = new Mock<System.Web.HttpRequestBase>();
request
.SetupGet(x => x.Headers)
.Returns(
new System.Net.WebHeaderCollection
{
{"X-Requested-With", "XMLHttpRequest"}
});
var context = new Mock<System.Web.HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
ValidCodeController target = new ValidCodeController();
target.ControllerContext =
new ControllerContext(context.Object, new RouteData(), target);
Params is a NameValueCollection property that can be set-up in a similar way to Headers:
var requestParams = new NameValueCollection
{
{ "FieldName", "value"}
};
request.SetupGet(x => x.Params).Returns(requestParams);
Another alternative to mocking the Context and all it's dependencies is to abstract the entire context/Params collection in a separate class, and mock that instead. In many cases this will make it easier, and avoids having to mock a complicated object graph:
Ex:
public void MainMethod()
{
var valueInQuestion = ISomeAbstraction.GetMyValue("FieldName");
}
You can now mock the GetMyValue method instead.

ControllerContext.HttpContext initialization for unit tests

In the process of writing an unit test for a Controller, I need to setup or initialize -
ControllerContext.HttpContext.Request.QueryString
What is the simplest way for setting this up so that I can actually pass the - ControllerContext.HttpContext.Request.QueryString - and have the controller tested?
You could use a mock framework in order to mock the HttpContext that the controller is working with.
For example with NSubstitute:
// arrange
var contextMock = Substitute.For<HttpContextBase>();
var requestMock = Substitute.For<HttpRequestBase>();
var queryString = new NameValueCollection();
queryString["foo"] = "bar";
requestMock.QueryString.Returns(queryString);
contextMock.Request.Returns(requestMock);
var sut = new SomeController();
sut.ControllerContext = new ControllerContext(contextMock, new RouteData(), sut);
// act
var actual = sut.SomeAction();
// assert
...
Of course you could have used any other mocking framework you like such as Moq or Rhino Mocks in order to mock the HttpContext.
By the way if you used view models (which you should always do in a properly designed ASP.NET MVC application) you wouldn't even cared about Request.QueryString because your controller action would now have taken a view model as argument:
public ActionResult SomeAction(MyViewModel model)
{
...
}
and the default model binder would have taken into account the binding of request parameters to model. You should avoid writing plumbing code in your controller action where you are extracting some data from query strings and stuffs.
Look how much more readable your unit test now becomes:
var model = new MyViewModel();
model.Foo = "bar";
var sut = new SomeController();
// act
var actual = sut.SomeAction(model);
// assert
...
See? You don't need to care about plumbing code anymore.

How do I make a unit test to test a method that checks request headers?

I am very, very new to unit testing and am trying to write a test for a pretty simple method:
public class myClass : RequireHttpsAttribute
{
public override void OnAuthorization(AuthoizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
var header = Convert.ToBoolean(request.Headers["Special-Header-Name"]);
if (!(header || request.IsSecureConnection))
{
HandleNonHttpsRequest(filterContext);
}
}
}
This method, which inherits from the RequireHttpsAttribute, checks if a certain header is present from a page, if it's missing or false, and the page is not secure, then it will call HandleNonHttpsRequest, otherwise it does nothing.
We are using Moq and Nunit for testing. I have found some resources to help build a fakeHttpContext with Moq, but honestly I'm not sure how to use it or where to go within my unit tests to ensure that fake HttpContexts are or are not causing the HandleNonHttpsRequest method to call.
I really appreciate any guidance with this issue.
// arrange
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var headers = new NameValueCollection
{
{ "Special-Header-Name", "false" }
};
request.Setup(x => x.Headers).Returns(headers);
request.Setup(x => x.HttpMethod).Returns("GET");
request.Setup(x => x.Url).Returns(new Uri("http://www.example.com"));
request.Setup(x => x.RawUrl).Returns("/home/index");
context.Setup(x => x.Request).Returns(request.Object);
var controller = new Mock<ControllerBase>();
var actionDescriptor = new Mock<ActionDescriptor>();
var controllerContext = new ControllerContext(context.Object, new RouteData(), controller.Object);
var filterContext = new AuthorizationContext(controllerContext, actionDescriptor.Object);
var sut = new myClass();
// act
sut.OnAuthorization(filterContext);
// assert
Assert.IsInstanceOfType(filterContext.Result, typeof(RedirectResult));
var redirectResult = (RedirectResult)filterContext.Result;
Assert.AreEqual("https://www.example.com/home/index", redirectResult.Url);
Yes, I'd use Moq and create a Mock<AuthorizationContext>. You'll need a series of mock objects to setup the fake request, most notably to specify a NameValueCollection of fake headers.
var request = new Mock<HttpRequestBase>();
request.SetupGet(c => c.Headers).Return(new NameValueCollection{ /* initialize values here */});
request.SetupGet(c => c.IsSecureConnection).Return(/*specify true or false depending on your test */);
var httpContext = new Mock<HttpContextBase>();
httpContext.SetupGet(c => c.Request).Return(request.Object);
var filterContext = new Mock<AuthorizationContext>();
filterContext.SetupGet(c => c.HttpContext).Return(httpContext.Object);
var myclass = new myClass();
myClass.OnAuthorization(filterContext.Object);
(sorry if syntax or usage is slightly off; doing this from the top of my head)
You may need to go in and mock any additional members on filterContext that HandleNonHttpsRequest invokes. I have two recommendations for going about this, as it can sometimes be a hassle if the method you are testing is doing lots of complex stuff on filterContext: 1) check visually and, if it's straight forward enough, mock all the invoked pieces 2) create the myClass.OnAuthorizationRequest, but don't implement any code yet other than the call to HandleNonHttpsRequest. Keep running the test and fixing missing/incorrectly mocked members until the test passes. Then implement your actual logic for OnAuthorizationRequest, testing and fixing (rinse repeat) until it passes.
I encountered an issue with the accepted solution using ASP.NET MVC 4. To resolve it I mocked the http context Items attribute otherwise the sut.OnAuthorization was causing an object is undefined exception:
MockHttpContext.Setup(x => x.Items)
.Returns(new System.Collections.Generic.Dictionary<object, object>());

Categories

Resources