21

I am having a hard time trying to test my API controller with Visual Studio 2013. My one solution has a Web API Project and a Test project. In my test project, I have a Unit Test with this:

[TestMethod]
public void GetProduct()
{
    HttpConfiguration config = new HttpConfiguration();
    HttpServer _server = new HttpServer(config);

    var client = new HttpClient(_server);

    var request = new HttpRequestMessage
    {
        RequestUri = new Uri("http://localhost:50892/api/product/hello"),
        Method = HttpMethod.Get
    };

    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    using (var response = client.SendAsync(request).Result)
    {
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

        var test = response.Content.ReadAsAsync<CollectionListDTO>().Result;
    }
}

I keep getting a 404. I have tried running my API with one instance of Visual Studio (IIS Express), and trying to debug this Unit Test in another instance. But no luck. I have verified that I can put this URL in a browser (when one Visual Studio is debugging) and I see my JSON response. But I can't figure out how to get it to work with my unit test and HttpClient. I have tried to find examples online but can't seem to find one. Can someone help?

UPDATE 1: I tried adding in a route but nothing happened.

HttpConfiguration config = new HttpConfiguration();

// Added this line
config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/product/hello/");

HttpServer _server = new HttpServer(config);

var client = new HttpClient(_server);

[...rest of code is the same]

Here is my API Controller

[HttpGet]
[Route("api/product/hello/")]
public IHttpActionResult Hello()
{
     return Ok();
}

UPDATE Resolution: I was able to get it to work if I new up HttpClient without a HttpServer object. I would still need to have two instances of VS running though. 1 running my API code and another to run the Unit Test.

Here is a working method.

[TestMethod]
public void Works()
{
    var client = new HttpClient(); // no HttpServer

    var request = new HttpRequestMessage
    {
        RequestUri = new Uri("http://localhost:50892/api/product/hello"),
        Method = HttpMethod.Get
    };

    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    using (var response = client.SendAsync(request).Result)
    {
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
    }
}

Anyone know why it doesn't work with a HttpServer and a HttpConfiguration passed into HttpClient? I have seen many examples that use this.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
duyn9uyen
  • 9,585
  • 12
  • 43
  • 54
  • 2
    might not make a difference, but why not use [GetAsync](https://msdn.microsoft.com/en-us/library/hh158944(v=vs.118).aspx) directly? – Alexandru Marculescu May 27 '16 at 07:04
  • Are you using OWIN pipeline with start up? You may need to use `TestServer`. https://blogs.msdn.microsoft.com/webdev/2013/11/26/unit-testing-owin-applications-using-testserver/ – Nkosi May 27 '16 at 09:14
  • I am not using owin. – duyn9uyen May 27 '16 at 11:14
  • what version of asp.net-mvc web api are you using? – Nkosi May 27 '16 at 12:54
  • During unit test there is not host for the `HttpServer`. which is why when you try to access the URL you get a 404. – Nkosi May 27 '16 at 13:09
  • @Nkos. I'm not sure I understand. What do I need to do so that I can make this http request? – duyn9uyen May 27 '16 at 13:16
  • 2
    Where is the code for the method you are trying to test? This looks like an integration test because you aren't mocking/faking out any dependencies. – Fran May 27 '16 at 13:30
  • Take a look at [this article](http://www.strathweb.com/2012/06/asp-net-web-api-integration-testing-with-in-memory-hosting/), doing what you are trying to do. I noticed that you are not configuring your `HttpConfiguration` to handle routes/requests. – Nkosi May 27 '16 at 14:07
  • Adding in a route to the config didn't seem to work. And yes @Fran, this is more an integration test. The end point works because I can put the url directly into a browser and I get a response. I just cant seem to get a successful HTTP request within my unit test. – duyn9uyen May 27 '16 at 15:10
  • @duyn9uyen update you post with what you tried. If you added the route like in the article and used the same url you had in your original post it's not going to work because it not going to match the routes – Nkosi May 27 '16 at 15:19
  • @Nkosi, I have added some updates. Thanks for checking. – duyn9uyen May 27 '16 at 16:43
  • @duyn9uyen Take a look at my answer. I was eventually able to get back to this question and provide you with an example. It's based on the information at the article I posted for you in a previous comment. – Nkosi May 29 '16 at 13:56
  • @duyn9uyen check updated answer and comments. got your code to work. – Nkosi May 31 '16 at 16:35
  • Why accessing data is not mentioned in the solution. We do not call an api just to check if the status is OK – Blue Clouds Mar 16 '18 at 12:42

1 Answers1

30

Referencing the following article I was able to do...

ASP.NET Web API integration testing with in-memory hosting

by working with a HttpServer and a HttpConfiguration passed into HttpClient. In the following example I created a simple ApiController that uses attribute routing. I configured the HttpConfiguration to map attribute routes and then passed it to the new HttpServer. The HttpClient can then use the configured server to make integration test calls to the test server.

public partial class MiscUnitTests {
    [TestClass]
    public class HttpClientIntegrationTests : MiscUnitTests {

        [TestMethod]
        public async Task HttpClient_Should_Get_OKStatus_From_Products_Using_InMemory_Hosting() {

            var config = new HttpConfiguration();
            //configure web api
            config.MapHttpAttributeRoutes();

            using (var server = new HttpServer(config)) {

                var client = new HttpClient(server);

                string url = "http://localhost/api/product/hello/";

                var request = new HttpRequestMessage {
                    RequestUri = new Uri(url),
                    Method = HttpMethod.Get
                };

                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                using (var response = await client.SendAsync(request)) {
                    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                }
            }
        }
    }

    public class ProductController : ApiController {
        [HttpGet]
        [Route("api/product/hello/")]
        public IHttpActionResult Hello() {
            return Ok();
        }
    }
}

There was no need to have another instance of VS running in order to integration test the controller.

The following simplified version of the test worked as well

var config = new HttpConfiguration();
//configure web api
config.MapHttpAttributeRoutes();

using (var server = new HttpServer(config)) {

    var client = new HttpClient(server);

    string url = "http://localhost/api/product/hello/";

    using (var response = await client.GetAsync(url)) {
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
    }
}

In your case you need to make sure that you are configuring the server properly to match your web api setup. This would mean that you have to register your api routes with the HttpConfiguration object.

var config = new HttpConfiguration();
//configure web api
WebApiConfig.Register(config);
//...other code removed for brevity
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Is there anyway you can send me a copy of this solution? I literally copied the code but it doesn't seem to work in my solution. So I think that have some configuration that is different. My project properties has the project running under IIS Express under a port number. The project url has this "http://localhost:50892/." I have tried using the port and without the port number in my unit test but no success. Still 404. – duyn9uyen May 31 '16 at 13:33
  • 1
    With in-memory testing requests are not actually hitting the network, so there is nothing to do with IIS or IIS Express. `HttpServer` a `DelegatingHandler` that is handling the request that the `HttpClient` is making. There is no need for a port and the default is `http://localhost` like in my examples. I took the exact code from your post, only changed the host of the url and configured the controller to use attribute routing. – Nkosi May 31 '16 at 13:51
  • Copy the exact unit test class as it is in my answer and run it in your test project. It should run and pass. You can then review how it was done and try to apply the same technique to you actual test. There was nothing extra. Everything you see there is exactly how it was written and tested. – Nkosi May 31 '16 at 13:58
  • I have uploaded the sample project here. I copied your code into the unit test. Does the unit test run successfully for you? I get a 404 with both tests. https://www.dropbox.com/s/0toamkkkrd50juq/WebApi_2.0_Sample.zip?dl=0 – duyn9uyen May 31 '16 at 14:14
  • I get green (test passes) every time i run it. If you have a GitHub account post the project up there and I'll take a look at your code. Downloading zip files is an issue for me. (security) – Nkosi May 31 '16 at 14:15
  • I have uploaded the project to my github. https://github.com/duyn9uyen/webapi2_0_In_memory_host_unit_test1 – duyn9uyen May 31 '16 at 14:53
  • Ok i downloaded it and was able to reproduce the errors your were getting. When i changed `config.MapHttpAttributeRoutes()` to use `WebApiConfig.Register(config);` it started to work. Rather strange for me because in my other projects it works fine with just the map attribute routing, even with the controller in another project. – Nkosi May 31 '16 at 15:30
  • Changing config.MapHttpAttributeRoutes() to use WebApiConfig.Register(config) worked! Thank you so much. Any ideas why config.MapHttpAttributeRoutes() doesn't work? – duyn9uyen May 31 '16 at 23:33
  • 1
    My guess is that `MapHttpAttributeRoutes` searches the current assembly for ApiControllers that have the Route specific attributes. When I copied the `ProductController` from the Sample project over to the test project it worked. I'll probably have to take a look at the source on github or codeplex and see what that method does. – Nkosi May 31 '16 at 23:37
  • 3
    I just had the exact same problem, when I only called `config.MapHttpAttributeRoutes()` my routes/controllers are not discovered. When I add an explicit reference to my WebApi project by calling for example `WebApiConfig.Register(config);`, it works! (only adding `var ctrl = new ValuesController();` also works!) My guess is that is has something to do with assemblies that are not used, are not loaded by the runtime. Which means that the WebApi is not loaded so the routes/controllers are not scanned and discovered – GerjanOnline Jun 20 '17 at 14:42
  • Is the point if using in-memory so that you don't have to create a duplicate Integration Test database? Or am I missing the point and you had to create one of those anyway? (this involves initializing integration test db, then subsequently destroying it for each integration test that is run) – Kyle Vassella Oct 22 '18 at 17:03
  • is there no way I can debug my API while running an integration test ? I put break points in my webapi controller, but they do not seems to get a hit. However test passes !! – kuldeep Mar 11 '20 at 17:08