I wrote a unit test for exception. But looks like it is not working correctly. It is always saying '404 Not Found' Status. that means url request not found. If I paste same url on browser it HttpResponse.StatusCode says BAD REQUEST.
I don't understand why it is not working for Unit test.
[TestMethod()]
public void GetTechDisciplinesTestException()
{
var config = new HttpSelfHostConfiguration("http://localhost:51546/");
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
using (var server = new HttpSelfHostServer(config))
using (var client = new HttpClient())
{
server.OpenAsync().Wait();
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:51546/api/techdisciplines/''"))
using (var response = client.SendAsync(request).Result)
{
//Here Response Status Code says 'Not Found',
//Suppose to be 'Bad Request`
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
}
server.CloseAsync().Wait();
};
}
I tried with HttpSelfHostServer which works fine and it uses IISExpress.
[TestMethod()]
public void GetTechDisciplinesTestException()
{
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:51546/api/techdisciplines/''"))
using (var response = client.SendAsync(request).Result)
{
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
}
};
}
So I dont know HttpSelfHostServer is not wkring in the code? How to force HttpSelfHostServer to use IISExpress? How to deal this?
Leaving aside why your specific method isn't working, could I suggest that you don't bother testing that particular behaviour via an HTTPRequest - just test directly against the controller class:
[TestMethod]
[ExpectedException(typeof(HttpResponseException))]
public void Controller_Throws()
{
try{
//setup and inject any dependencies here, using Mocks, etc
var sut = new TestController();
//pass any required Action parameters here...
sut.GetSomething();
}
catch(HttpResponseException ex)
{
Assert.AreEqual(ex.Response.StatusCode,
HttpStatusCode.BadRequest,
"Wrong response type");
throw;
}
}
Since this way you are truly "unit testing" that behaviour on the controller, and avoiding any indirect tests
For example if your controller goes off and tries to hit a database before you throw your HttpResponseException, then you're not really testing the controller in isolation -- because if you did get an exception back you'd not be 100% certain what threw it.
By testing directly you can inject for e.g. Mock dependencies that will do nothing other than what you tell them to do.
Related
I have the following controller method:
public IHttpActionResult GetAttractions([FromUri] SearchAttractionRequest request)
{
try
{
var requestParameterType = request.RequestType();
var searchResultModel = _attractionService.GetAttractions(request);
return TransformListToResult(searchResultModel.Results, request.PerPage, searchResultModel.TotalItemCount, searchResultModel.GeneratedQuery);
}
catch (Exception ex)
{
// return Content(HttpStatusCode.BadRequest, "Any object");
// return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));
}
}
When making the call request.RequestType() if the parameter passed in is invalid an argument exception will be thrown and code will end up in catch block. I have a unit test that I am trying to write:
[Test]
public void Given_Mixed_Parameters_Expect_Error()
{
var invalidQuery = new SearchAttractionRequest
{
Category = Guid.NewGuid().ToString() + "|a catgeory|" + Guid.NewGuid().ToString(),
Genre = "",
Region = ""
};
var attractionServiceMock = Mock.Of<IAttractionService>();
var sut = new AttractionsApiController(attractionServiceMock);
Action act = () => sut.GetAttractions(invalidQuery);
act.ShouldThrow<HttpResponseException>();
}
Basically I want to test that when I send in invalid param I get back error response. So what do i need to send back from my api method and what do i need todo in my test to test for the error. At the moment I am getting error in my test becuase the expected error is argument exception, which is correct but i want to mimic what a client would get back.
Do I need todo something like this in my test:
as OkNegotiatedContentResult<SomeObject>;
You can't exactly write a test for the controller that will mimic what the client receives. That's because, by design, the controller is just a class with methods that return some result. Technically it doesn't determine what the client receives.
For example, if you call a method that throws an unhandled ArgumentException, other middleware is going to determine that the client should receive a 500 response. The controller isn't doing that.
Or, a controller might return a ViewResult. The client doesn't get a ViewResult - it gets HTML. The controller doesn't determine what that HTML will be.
When you're writing unit tests for the controller, think of it as just another class. You're just testing that it returns what it's supposed to.
If the controller throws an unhandled ArgumentException you can test for that.
If you handle the ArgumentException and return
Content(HttpStatusCode.BadRequest, "Any object");`
You could do this:
var result = (NegotiatedContentResult<string>)controller.GetResult();
Assert.AreEqual(HttpStatusCode.BadRequest, result.StatusCode);
I believe you could use the following:
var attractionServiceMock = Mock.Of<IAttractionService>();
var sut = new AttractionsApiController(attractionServiceMock);
var result = sut.GetAttractions(invalidQuery) as BadRequestErrorMessageResult;
Assert.IsNotNull(result);
I've tried many different approaches for the past couple of hours, but my method call is hanging up the thread.
Here is my Web API code, which works fine when making AJAX call from the MVC client, but I'm trying to test calling from the server:
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Below is my MVC controller code and model code:
public async Task<ActionResult> TestApi()
{
try
{
var result = await VoipModels.GetValues();
return MVCUtils.JsonContent(result);
}
catch (Exception ex)
{
return MVCUtils.HandleError(ex);
}
}
...
public static async Task<string[]> GetValues()
{
string[] result = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44305/api/");
//THIS IS THE LINE THAT HANGS UP - I'VE TRIED MANY VARIATIONS
var response = await client.GetAsync("values", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsAsync<string[]>();
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
return result;
}
I've used this format successfully when calling a separate, 3rd party API. I've run out of examples to try from my couple of hours of Googling.
What am I doing wrong here?
Check your port number. Based on your code, you have "hard coded" the port "http://localhost:44305/api/" which may likely be incorrect, you should convert that to grab it from the host
configuration instead.
Check your local machine's firewall. Make sure that your local machine's firewall is allowing connections to the port assigned.
Check your protocol. Ensure that you are using http or https appropriately in your request URL.
As a special note, there are very rare cases / exception cases that you would want to have a web API server call itself. Doing so, is rather inefficient design as it will consume resources for no gain (such a generating request and response).
I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
We have SOA for our solution. We are using .net framework 4.5.1, asp.net mvc 4.6, sql server, windows server and thinktecture identity server 3 ( for token based webapi calls. )
Solution structure looks like;
Our mvc frontend application talks with our webapi application via a httpClient wrapper. Here is the generic http client wrapper code;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace Cheetah.HttpClientWrapper
{
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
public ResourceServerRestClient(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public string BaseAddress { get; set; }
public Task<T> GetAsync<T>(string uri, string clientId)
{
return CheckAndInvokeAsync(async token =>
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
});
}
private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
{
if (!string.IsNullOrEmpty(resourceServiceClientName))
{
client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
}
if (string.IsNullOrEmpty(BaseAddress))
{
throw new Exception("BaseAddress is required!");
}
client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 10);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
}
private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
catch (Exception ex)
{
if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized)
{
string token = await _tokenProvider.GetTokenAsync();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
}
throw;
}
}
public void ThrowResourceServerException(List<string> messages)
{
string message = messages.Aggregate((p, q) => q + " - " + p);
var exception = new Exception(message);
exception.Data.Add("ServiceOperationException", message);
throw exception;
}
}
}
Also, sometimes this http client wrapper using with NitoAsync manager ( Call async methods as sync. ), and sometimes we are using this generic method directly with await - async task wait like;
var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id);
So here is our problem:
When we test our mvc application with jmeter (for making some-kind-of load test / 10 threads per 1 sec), after a couple of minutes, mvc application stops working [ exception is task canceled due to timeout ] ( maybe only 1-2 requests timeouts ) on this line: HttpResponseMessage response = await client.GetAsync(uri);. But after that request, all requests will be failed like they are in row. So mvc application is hanging for 2-15 minutes ( randomly ) but in that time I can send new requests from postman to webapi. They are ok, I mean webapi is responding well. After a couple of minutes mvc application turnback to normal.
Note: We have load-balancer for mvc-ui and webapi. Because sometimes we get 120K requests in a minute in a busy day. But it gives same error if there is no load balancer in front of webapi or mvc application. So it's not LB problem.
Note2: We tried to use RestSharp for mvc-ui and webapi communication. We got same error here. When a reuqest is failing, all requests will be failed in a row. It looks like it's a network error but we can't find a proof for it.
Can you see any error on my httpClient wrapper ? or better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
Update1: We moved projects .net 4.5.1 to 4.6.1. Same deadlock happened again. And than we temporary moved all source codes of the layer: "Business & Repository" as dll level. There is no webapi between business & presentation level now. Dead lock solved. We are still searching why httpClientWrapper codes are not working properly when we called webapi methods from our webapplication controllers.
better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
A best practice here is for the client (browser in your case) to directly retrieve data from the Web API Controllers and for the MVC controllers to only serve pure HTML views which include layout, styles (css), visual structure, scripts (ie. javascript) etc and not the data.
Image credit: Ode to Code. Incidentally the author on that site also does not recommend your approach although it is listed as an option.
This servers as a good SOC between your views and your data allowing you more easily to make changes to either part.
It allows for the client (browser) to retrieve data asynchronously which creates for a better user experience.
By not doing this and adding a network request step in the call stack you have created an unnecessary expensive step in the flow of data (call from MVC Controller(s) to Web API deployment). The more boundaries are crossed during executing the slower the execution.
The fast solution, as you have already figured out, is to call your business code library directly from your MVC project. This will avoid the additional and unnecessary network step. There is nothing wrong with doing this and many more traditional sites serve both the view (html) and data in the same call. It makes for a more tightly coupled design but it is better than what you had.
The best long term solution is to change the MVC views so they call your Web API deployment directly. This can be done using frameworks like Angular, React, Backbone, etc. If the Web API method calls are limited and are not expected to grow you can also use JQuery or pure javascript BUT I would not try to build a complex application on this, there is a reason why frameworks like Angular have become so popular.
As to the actual underlying technical problem in this case we can't be sure without a memory dump to see what resources are causing the deadlock. It might be something as simple as making sure your MVC Action Methods are also returning async Task<ActionResult> (instead of just ActionResult which, I am guessing, is how you have them structured now) so they can call the HttpClient using an actual async/await pattern. Honestly, because its a bad design, I would not spend any time into trying to get this to work.
I'm not exactly sure whu, but I'll start by refactoring the GetAsync() method
public async Task<T> GetAsync<T>(string uri, string clientId)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
}
else
{
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
}
catch (Exception ex)
{
throw;
}
}
You should put .ConfigureAwait(false) to your inner awaits statements:
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
(...)
return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
(...)
string token = await _tokenProvider.IsTokenNullOrExpired().ConfigureAwait(false);
(...)
return await method(token).ConfigureAwait(false);;
(...)
string token = await _tokenProvider.GetTokenAsync().ConfigureAwait(false);;
(...)
return await method(token).ConfigureAwait(false);
This way you will avoid to capture the synchronization context before the await is done. Otherwise the continuation will be done in this context, which might result in a lock if this one is in use by other threads.
Doing so will allow the continuation to be done whithin the context of the task which is awaited.
Doing an integration test on a web api endpoint what should I put my focus on to assert?
My endpoint is also doing a call to a domain service.
Should I mock that service? With the current code that is not possible, because I would need to instantiate the controller to pass the mock service.
Am I interested in the service return value? actually not.
I am only interested wether the endpoint was succesfully triggered but then I should isolate the service call I guess.
Any advice is welcome :-)
TEST
[TestClass]
public class SchoolyearControllerTests
{
private TestServer _server;
[TestInitialize]
public void FixtureInit()
{
_server = TestServer.Create<Startup>();
}
[TestCleanup]
public void FixtureDispose()
{
_server.Dispose();
}
[TestMethod]
public void Get()
{
var response = _server.HttpClient.GetAsync(_server.BaseAddress + "/api/schoolyears").Result;
var result = response.Content.ReadAsAsync<IEnumerable<SchoolyearDTO>>().GetAwaiter().GetResult();
Assert.AreEqual(response.StatusCode, HttpStatusCode.OK);
}
}
Action to test
[HttpGet]
public async Task<IHttpActionResult> Get()
{
var schoolyears = await service.GetSchoolyears();
return Ok(schoolyears);
}
The trouble with doing an integration test on a web service is that it doesn't tell you very much about the problem - or even if there actually is one, and if there is, it doesn't tell you where the issue lies. It will either succeed or fail. So in that respect did you get a 200 response code or a 500 response code... but did it fail because:
The server was unreachable
The web service isn't started, or failed when trying to start
A firewall is blocking the network
A database record wasn't found
There's a database schema problem and entity framework didn't start
properly.
It could literally be anything - and result might be different on your dev machine than in production - so what does it really tell you about your application?
What makes for robust software is to test that your product is able to handle any of these situations correctly, gracefully and robustly.
I write my controller actions like this:
public HttpResponseMessage Get(int id)
{
try
{
var person = _personRepository.GetById(id);
var dto = Mapper.Map<PersonDto>(person);
HttpResponseMessage response = Request.CreateResponse<PersonDto>(HttpStatusCode.OK, dto);
return response;
}
catch (TextFileDataSourceException ex)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError);
return response;
}
catch (DataResourceNotFoundException ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
return response;
}
catch (FormatException ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return response;
}
catch (Exception ex)
{
HttpResponseMessage response = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return response;
}
}
A try block gets the data, makes a dto and returns the data with a 200 code. There are several error conditions handled here, but none indicate a problem with my web service itself and some (404 error) don't even indicate an issue with the application - I EXPECT a NotFoundException and a 404 if my application can't find a record - if this happens my application WORKS in this scenario.
So if any of these error conditions occur, its not because there's a problem with the web service, and not necessarily a problem with the app. But I can test that my web service is returning the correct response for any of these expected conditions.
The tests for this controller action looks like this:
[Test]
public void CanGetPerson()
{
#region Arrange
var person = new Person
{
Id = 1,
FamilyName = "Rooney",
GivenName = "Wayne",
MiddleNames = "Mark",
DateOfBirth = new DateTime(1985, 10, 24),
DateOfDeath = null,
PlaceOfBirth = "Liverpool",
Height = 1.76m,
TwitterId = "#WayneRooney"
};
Mapper.CreateMap<Person, PersonDto>();
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Returns(person);
var controller = new PersonController(mockPersonRepository.Object);
controller.Request = new HttpRequestMessage(HttpMethod.Get, "1");
controller.Configuration = new HttpConfiguration(new HttpRouteCollection());
#endregion
#region act
HttpResponseMessage result = controller.Get(1);
#endregion
#region assert
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
#endregion
}
[Test]
public void CanHandlePersonNotExists()
{
#region Arrange
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Throws<DataResourceNotFoundException>();
var controller = new PersonController(mockPersonRepository.Object)
{
Request = new HttpRequestMessage(HttpMethod.Get, "1"),
Configuration = new HttpConfiguration(new HttpRouteCollection())
};
#endregion
#region Act
HttpResponseMessage result = controller.Get(1);
#endregion
#region Assert
Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode);
#endregion
}
[Test]
public void CanHandleServerError()
{
#region Arrange
var mockPersonRepository = new Mock<IPersonRepository>();
mockPersonRepository.Setup(x => x.GetById(1)).Throws<Exception>();
var controller = new PersonController(mockPersonRepository.Object);
controller.Request = new HttpRequestMessage(HttpMethod.Get, "1");
controller.Configuration = new HttpConfiguration(new HttpRouteCollection());
#endregion
#region Act
HttpResponseMessage result = controller.Get(1);
#endregion
#region Assert
Assert.AreEqual(HttpStatusCode.InternalServerError, result.StatusCode);
#endregion
}
Note that I'm introducing a mock respository and having my mock repository trigger the expected exceptions for 404 and server error and ensuring that the web service handles it correctly.
What this tells me is that my web service handling expected and exceptional situations as it should and returning the appropriate codes: 200/404/500.
Although some are error states and some are success states, none of these outcomes indicate a problem with my web service - it is behaving exactly as it should, and that's what I want to test.
An 'across the network' integration test on a web service tells you nothing about the robustness or correctness of your application - or even if its returning the correct data or response-code.
Don't try to re-test WebAPI... Microsoft wrote a massive suite of tests for it already - hundreds of test-fixture classes, thousands of test methods:
https://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Http.Test/Controllers/ApiControllerTest.cs
Assume that WebAPI works as it should and doesn't require you to test it again. Focus on testing your application code and make sure success and error conditions are handled gracefully by your web service.
If you want to check your web service is connected and available on the network - open a browser and test it manually; there's no need to automate this; the result will vary between environments and according to external conditions.
Test each layer of your application the same way, mock out the layer above and test that the current layer handles every possible outcome from the layer above.
Your client application for your service should do the same thing: Mock the web service, and pretend that it gave a 404 - and check that it handles this as it should.