I'm trying to unit test a controller that is catching a FlurlHttpException and calling GetResponseJson<TError>() to get the error message in the catch block. I attempted to mock the exception, but the Call property does not allow me set the Settings. When the unit test runs it fails because there isn't a JsonSerializer in the settings. How do I setup this test?
Here's my current attempt that does not work:
Controller
[Route]
public async Task<IHttpActionResult> Post(SomeModel model)
{
try
{
var id = await _serviceClient.Create(model);
return Ok(new { id });
}
catch (FlurlHttpException ex)
{
if (ex.Call.HttpStatus == HttpStatusCode.BadRequest)
return BadRequest(ex.GetResponseJson<BadRequestError>().Message);
throw;
}
}
Unit Test
[TestMethod]
public async Task Post_ServiceClientBadRequest_ShouldReturnBadRequestWithMessage()
{
//Arrange
string errorMessage = "A bad request";
string jsonErrorResponse = JsonConvert.SerializeObject(new BadRequestError { Message = errorMessage });
var badRequestCall = new HttpCall
{
Response = new HttpResponseMessage(HttpStatusCode.BadRequest),
ErrorResponseBody = jsonErrorResponse
//This would work, but Settings has a private set, so I can't
//,Settings = new FlurlHttpSettings { JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()) }
};
_mockServiceClient
.Setup(client => client.create(It.IsAny<SomeModel>()))
.ThrowsAsync(new FlurlHttpException(badRequestCall, "exception", new Exception()));
//Act
var result = await _controller.Post(new SomeModel());
var response = result as BadRequestErrorMessageResult;
//Assert
Assert.IsNotNull(response);
Assert.AreEqual(errorMessage, response.Message);
}
If you are encapsulating the usage of Flurl within your ServiceClient object, then I think catching FlurlException, extracting Message, and returning a more appropriate exception should also be encapsulated in that service. This will make your controller much easier to test.
Related
I have a Web API written in asp.net core for which I am writing the integration tests. For any validation, I am generating a custom exception e.g. DatabaseEntityNotFoundException, DataValidationException, etc which is handled using Exception Middleware.
The following is one of my integration tests where I am trying to test the custom DatabaseEntityNotFoundException scenario i.e.
[Fact]
public async Task RegisterCompanyWithUser_Should_ThrowErrorIfTheUserDoesnotExist()
{
// Arrange
var repositoryManager = WebAppFactory.GetRepositoryManager();
var companyWithRegistrationDto = new CompanyWithUserForRegistrationDto
{
CompanyName = Randomizer.GenString(),
Address = Randomizer.GenString(),
OwnerName = Randomizer.GenString(),
PhoneContact = Randomizer.GenPhoneNumber(),
CompanyEmail = Randomizer.GenEmail(),
TaxNumber = string.Empty,
UserId = Guid.NewGuid(),
User = null,
};
var postRequest = new HttpRequestMessage(
HttpMethod.Post,
"api/companies/register"
);
postRequest.Content = JsonContent.Create(
companyWithRegistrationDto,
MediaTypeHeaderValue.Parse("application/json")
);
// Act
var errorRawResponse = await httpClient.SendAsync(postRequest);
var errorDetails = await errorRawResponse.Content.ReadFromJsonAsync<ErrorDetails>();
}
Exception middleware is under the following i.e.
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(
this IApplicationBuilder app,
ILoggerManager logger
)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
context.Response.ContentType = "application/json";
if (contextFeature != null)
{
context.Response.StatusCode = GetHttpStatusCode(contextFeature.Error);
logger.LogError($"Something went wrong {contextFeature.Error}");
await context.Response.WriteAsync(
new ErrorDetails
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error",
Identifier = GetIdentifierIfDataValidationException(contextFeature.Error)
}
.ToString()
);
}
});
});
}
private static string GetIdentifierIfDataValidationException(Exception exception)
=>
exception is DataValidationException
? ((DataValidationException)exception).Identifier
: string.Empty;
public static int GetHttpStatusCode(Exception exception)
{
if (exception is DatabaseEntityNotFoundException)
{
return (int)((DatabaseEntityNotFoundException)exception).StatusCode;
}
else
if (exception is DataValidationException)
{
return (int)((DataValidationException)exception).StatusCode;
}
return (int)HttpStatusCode.InternalServerError;
}
}
When the exception is generated by the controller it is safely handled by the middleware but throws the exception as soon as it goes out of Exception middelware context when using default version of SendAsync(HttpRequestMessage) i.e.
System.Net.Http.HttpRequestException : Error while copying content to a stream.
I googled it where I found using an overloaded version of SendAsync(HttpRequestMessage, HttpCompletionOption).
I tried passing HttpCompletionOption.ResponseHeadersRead that does execute sendAsync line but throws the same error on the line after it where I am trying to Deserialized object into ErrorDetails i.e.
await errorRawResponse.Content.ReadFromJsonAsync<ErrorDetails>();
I have read about HttpCompletionOption.ResponseHeadersRead where It states as soon It reads the header info it returns but I am curious that why my specific custom exception DatabaseEntityNotFound is unable to be deserialized where the same code can be deserialized by another custom exception DataValidationException when using default version of SendAsync(HttpRequestMessage).
Kindly guide me on this where I am wrong.
I am creating an asp.net core web app, which creates routes from user defined sources (config, database etc). I have used a middleware factory based approach which works fine, except I do not know how to isolate a middleware for unit/integration testing. I use the route data from the HttpContext to validate the incoming request against the user defined configuration source, so I need the HttpContext to have RouteData, which is why I am leaning towards Integration testing with xUnit.
If I make a request using a httpclient created via AspNetCore.TestHost.TestServer.CreateCLient(), I work my way through my entire call chain, which is what I expect. What I need to be able to do is terminate the middleware or provide the HttpContext with RouteData.
I have tried an ugly unit test using AutoFixture and the DefaultHttpContext(), but as expected, I don't get any RouteData on my HttpContext, so my tests can never pass.
Middleware
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
if (_flowApis.Count == 0)
throw new ArgumentNullException();
var doesMatch = false;
var routeData = context.GetRouteData();
foreach (var api in _flowApis)
{
if (!RequestType.IsValidRequestType(api.Request.Type))
throw new InvalidOperationException();
else
foreach (Route route in routeData.Routers.Where(r => r.GetType() == typeof(Route)))
{
if (route.RouteTemplate == api.Route)
{
if (route.Constraints.TryGetValue("httpMethod", out IRouteConstraint routeConstraint))
{
var value = (HttpMethodRouteConstraint)routeConstraint;
if (value.AllowedMethods.Contains(api.Method))
{
doesMatch = true;
var routeValues = GetRouteValues(routeData);
var request = new Core.Request(context.Request, api, routeValues);
context.Items.Add(nameof(Core.Request), request);
break;
}
}
}
}
if (doesMatch)
break;
}
if (!doesMatch)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("", new CancellationToken());
}
else
await next(context);
}
catch(ArgumentNullException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Flow Configuration in app.settings has not been set.", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
catch (InvalidOperationException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Invalid Request Type", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
}
Unit Test
[TestMethod]
public async Task Request_path_matches_an_api_route_and_method()
{
var fixture = ScaffoldFixture();
var flowApi = fixture.Build<FlowApi>()
.With(p => p.Route, "/some/path")
.With(m => m.Method, "GET")
.Create();
var flowApiRequest = fixture.Build<Request>()
.With(t => t.Type, "CData")
.Create();
flowApi.Request = flowApiRequest;
var flowConfiguration = fixture.Create<FlowConfiguration>();
flowConfiguration.FlowApis.Clear();
flowConfiguration.FlowApis.Add(flowApi);
var middleware = new MatchRouteToFlowApiMiddleware(flowConfiguration: flowConfiguration);
var context = new DefaultHttpContext();
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
context.Items.ContainsKey(nameof(Core.Request)).Should().BeTrue();
}
Integration test
[Fact]
public async Task Request_path_matches_an_api_route_and_method()
{
//Arrange
var response = await _httpClient.GetAsync("/some/route");
response.StatusCode.Should().Be(500);
}
When executing the unit test, I can never satisfy the inner for each beacuse I don't have route data on the HttpContext.
When executing the integration test, the middleware executes as expected but since it does not terminate, i cannot validate
I realised that trying to terminate my middleware just to validate it using xUnit meant I would not be testing my system as is, so I looked more at my unit test setup.
I have managed to get Route Data on the HttpContext. It feels messy but is a starting point at least.
private static DefaultHttpContext DefaultHttpContextWithRoute(Fixture fixture)
{
var context = new DefaultHttpContext();
var routeDictionary = new RouteValueDictionary
{
{ "some","path" }
};
context.Features.Set<IRoutingFeature>(new RoutingFeature());
context.Features.Get<IRoutingFeature>().RouteData = new RouteData(routeDictionary);
var inline = fixture.Create<DefaultInlineConstraintResolver>();
var route = new Route(new TestRouter(), "/some/path", inline);
var httpMethodRouteConstraint = new HttpMethodRouteConstraint("GET");
route.Constraints.Add("httpMethod", httpMethodRouteConstraint);
context.Features.Get<IRoutingFeature>().RouteData.Routers.Add(route);
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
return context;
}
private class TestRouter : IRouter
{
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
throw new NotImplementedException();
}
public Task RouteAsync(RouteContext context)
{
throw new NotImplementedException();
}
}
In each unit test I call the DefaultHttpContext and use invoke my middleware like so
DefaultHttpContext context = DefaultHttpContextWithRoute(fixture);
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
I'm trying to learn unit testing in xUnit for ASP.NET Core projects. In order to achieve it, I have created simple ASP.NET Core project to perform tests on it. However I cannot get 100% cover of tests due to wrong testing an exception that's being caught inside controller method.
Here is my controller method I'm testing:
[HttpGet]
public async Task<IEnumerable<User>> GetUsers()
{
try
{
var users = await _repository.User.GetAllUsersAsync();
return users;
}
catch (Exception e)
{
_logger.LogError($"Error in GetUsers: {e}");
return null;
}
}
And here is my unit test method in xUnit:
[Fact]
public async Task GetUsers_WhenCalled_ReturnsCorrectAmountOfUsers()
{
//Arrange
var mockRepo = new Mock<IRepositoryWrapper>();
mockRepo.Setup(repo => repo.User.GetAllUsersAsync())
.ReturnsAsync(GetTestUsers());
var controller = new UsersController(mockRepo.Object, _logger, _service);
//Act
var result = await controller.GetUsers();
//Assert
var model = Assert.IsAssignableFrom<IEnumerable<User>>(result);
model.Count().Should().Be(3);
Assert.Throws<NullReferenceException>(() =>
_controller.GetUsers().Exception);
}
When I run tests, everything gets green status, however inside the controller class I cannot see a 'green tick' next to the lines with catch block scope. I'd really like to know how to write proper code for testing an exceptions inside catch blocks!
Another test is needed that will cause the exception to be thrown when being exercised.
For example
[Fact]
public async Task GetUsers_WhenCalled_HandlesException() {
//Arrange
var mockRepo = new Mock<IRepositoryWrapper>();
mockRepo
.Setup(repo => repo.User.GetAllUsersAsync())
.ThrowsAsync(new InvalidOperationException());
var controller = new UsersController(mockRepo.Object, _logger, _service);
//Act
var result = await controller.GetUsers();
//Assert
Assert.IsNull(result);
//Can also assert what logger records
}
In the above example, when GetAllUsersAsync is invoked, it will throw an exception that will be caught in the try-catch and allow the code to flow as intended for the test.
I was trying to write some unit tests for an web api action method while exception. So below my action method
[Route("{userName}/{searchCriteria}")]
[HttpGet]
public IHttpActionResult Events(string accountNumber, string searchCriteria)
{
try
{
bool isInputValid = _inputValidation.IsTrackingEventInputValid(accountNumber, searchCriteria);
if (isInputValid)
{
return OK ("my data");
}
else
{
throw new ArgumentException();
}
}
catch (ArgumentException ae)
{
return new ResponseMessageResult(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ExceptionHandlingMessages.InvalidArgumentException));
}
catch (Exception ex)
{
return new ResponseMessageResult(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ExceptionHandlingMessages.InternalServerError));
}
}
I want to check responds status code and responds messages while exception occurs. But problem is as soon as my execution hits ResponseMessageResult code it throws another ArgumentNullException saying Value cannot be null.Parameter name: request. Because of that control never returns to my unit test method.
My unit test method as
[TestMethod]
public void Events()
{
_mockInputValidation.Setup(x => x.IsTrackingEventInputValid(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
//act
IHttpActionResult actionResult = _trackingEventController.Events(string.Empty, string.Empty);
//assert
}
I also tries putting [ExpectedException(type)] but not much helpful
how can I solve this
Refactor your code to try and avoid throwing exceptions in your actions. Let the exception handler/filter handle them (cross-cutting concerns). Your original issue could have happened if you did not provide a proper request message for unit test.
[Route("{userName}/{searchCriteria}")]
[HttpGet]
public IHttpActionResult Events(string accountNumber, string searchCriteria) {
bool isInputValid = _inputValidation.IsTrackingEventInputValid(accountNumber, searchCriteria);
if (isInputValid) {
return Ok("my data");
} else {
return BadRequest(ExceptionHandlingMessages.InvalidArgumentException);
}
}
And then for the particular test case
[TestMethod]
public void IsTrackingEventInputValid_When_False_Should_Return_BadRequest() {
//Arrange
_mockInputValidation.Setup(x => x.IsTrackingEventInputValid(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
var expected = ExceptionHandlingMessages.InvalidArgumentException;
//Act
var actionResult = _trackingEventController.Events(string.Empty, string.Empty) as BadRequestErrorMessageResult;
//Assert
Assert.IsNotNull(actionResult);
Assert.AreEqual(expected, actionResult.Message);
}
I'm having a problem with an async method that I implemented. The method basically makes a HttpRequest to a resource and deserializes the string if the request is successful. I wrote a test for the method, and it works. But the method does never return when I call it from a controller?
public async Task<IEnumerable<T>> Get()
{
try
{
var resourceSegmentUri = new Uri(_uri, UriKind.Relative);
var response = await _client.GetAsync(resourceSegmentUri);
if (response.IsSuccessStatusCode)
{
var submission = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<T>>(submission);
}
if (response.Content != null)
{
var message = response.Content.ReadAsStringAsync();
throw new WebException(message.Result, (WebExceptionStatus)response.StatusCode);
}
}
catch (WebException e)
{
Logger.Error("GET Request failed with status: {0}", e.Status);
throw;
}
throw new Exception();
}
Code that never returns:
public ActionResult Index()
{
var api = new Api();
var test = api.Get().Result; //Never returns
return View();
}
Test that works:
[Test]
public void GetShouldReturnIfSuccessfulRequest()
{
var api = new Api();
var submission = api.Get();
Console.WriteLine(JsonConvert.SerializeObject(submission));
Assert.NotNull(submission);
}
Does anyone know the problem?
You've got a deadlock because you're calling .Result in your controller action.
If you use async/await then you have to use asynchronous actions too.
So something like this should fix it:
public async Task<ActionResult> Index()
{
var api = new Api();
var test = await api.Get(); // Should return
}
There's a comprehensive article about this here: Using Asynchronous Methods in ASP.NET MVC 4