I noticed the nice self-hosting option of the WebAPI. How can I utilize this in my unit/integration tests to ensure that if I hit a particular URL with a particular verb and a particular media type that I get back what I expect?
I tried setting up a server in a base class of my test assembly like so:
public class BaseTest
{
protected HttpSelfHostServer Server { get; set; }
[SetUp]
public void SetUp()
{
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.Routes.MapHttpRoute(
"API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
Server = new HttpSelfHostServer(config);
Server.OpenAsync().Wait();
}
[TearDown]
public void TearDown()
{
Server.CloseAsync();
Server.Dispose();
}
}
When I try calling to the server like so:
[TestFixture]
public class WhenWorkingWithProducts : BaseTest
{
[Test]
public void ThenShouldGetAllProductsSuccessfully()
{
var request = new HttpRequestMessage(HttpMethod.Get, @"http://localhost:8080/api/products/");
CancellationToken cancellationToken = new CancellationToken();
var response = Server.SubmitRequestAsync(request, cancellationToken);
response.Wait();
var result = response.Result;
result.StatusCode.ShouldEqual(System.Net.HttpStatusCode.OK);
}
}
I get back a 404 instead of a 200. However, when I hit the same URL by debugging the project i get back what I expect. What am I missing here? :)
From your code, I see that you are mixing couple of things:
1. You could use HttpSelfHostServer entirely without any network communication to submit messages up the service stack using the SubmitRequestAsync(HttpRequestMessage, CancellationToken).
2. You can use the HttpClient -> ...content on network....-> HttpSelfHostServer -> message handlers->Dispatcher->..rest of the stack.
I would prefer option 2. above. A sample standalone test written in XUnit is below (I hope you can relate this to the test framework that you are using).
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
using System.Web.Http.SelfHost;
using Xunit;
public class SampleTests : IDisposable
{
[Fact]
public void PostTest()
{
string expectedResponse = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace("'", "\"");
HttpClient httpClient = new HttpClient();
HttpRequestMessage<string> request = new HttpRequestMessage<string>("Hello", new MediaTypeHeaderValue("application/xml"));
request.RequestUri = new Uri(this.baseAddress + "/api/SampleTests/");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
request.Method = HttpMethod.Post;
HttpResponseMessage response = httpClient.SendAsync(request).Result;
Assert.NotNull(response.Content);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType);
Assert.Equal<string>(expectedResponse, response.Content.ReadAsStringAsync().Result);
}
[Fact]
public void GetTest()
{
string expectedResponse = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace("'", "\"");
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(this.baseAddress + "/api/SampleTests/");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
request.Method = HttpMethod.Get;
HttpResponseMessage response = httpClient.SendAsync(request).Result;
Assert.NotNull(response.Content);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType);
Assert.Equal<string>(expectedResponse, response.Content.ReadAsStringAsync().Result);
}
private HttpSelfHostServer server = null;
private string baseAddress = null;
// Setup
public SampleTests()
{
baseAddress = string.Format("http://{0}:9090", Environment.MachineName);
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.baseAddress);
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
}
//tear down
public void Dispose()
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
public class SampleTestsController : ApiController
{
public string Post(string input)
{
return input;
}
public string Get()
{
return "Hello";
}
}
I may have been a little bit hasty marking this as answered.
In the example code you have, the controller is directly in the test project. However, in real situations this isn't the case. In my example my controller is in another assembly (project in this case).
In this situation I cannot seem to get the test to hit the proper controller.
Actually we currently have a bug regarding this where Controllers present in separate dlls are not found if those dlls are not loaded into memory yet...this bug is in SelfHost scenario only...but there is a workaround you could probably use:
So, assuming that "SampleTestsController" is in another dll...you could add the following line in your test, which will force the Dll to be loaded into the memory:
Type sampleTestsControllerType = typeof(OtherLibrary.SampleTestsController);
Is it possible to pass in a json string as the content of your Request and have the server parse it and convert it to the Type that your Post method requires?
Here is a code example that explains what I am trying to say. The PostAsString test fails and the PostAsClass test passes. I want to know how to get the PostAsString test to pass.
[TestClass]
public class SampleTests
{
private HttpSelfHostServer server = null;
private string baseAddress = null;
//Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
baseAddress = string.Format("http://{0}:9090", Environment.MachineName);
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.baseAddress);
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
}
//Use TestCleanup to run code after each test has run
[TestCleanup()]
public void MyTestCleanup()
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
[TestMethod]
public void GetTest()
{
string expectedResponse = "{'ID':1,'Name':'Saved Item'}".Replace("'", "\"");
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Get;
HttpResponseMessage response = httpClient.SendAsync(request).Result;
Assert.IsNotNull(response.Content);
Assert.IsNotNull(response.Content.Headers.ContentType);
Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType);
Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result);
}
[TestMethod]
public void PostAsString()
{
string expectedResponse = "{'ID':2,'Name':'New Item'}".Replace("'", "\"");
HttpClient httpClient = new HttpClient();
string msgContent = "{'Name':'New Item'}".Replace("'", "\"");
HttpRequestMessage<string> request = new HttpRequestMessage<string>(msgContent, new MediaTypeHeaderValue("application/json"));
request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
HttpResponseMessage response = httpClient.SendAsync(request).Result;
Assert.IsNotNull(response.Content);
Assert.IsNotNull(response.Content.Headers.ContentType);
Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType);
Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result);
}
[TestMethod]
public void PostAsClass()
{
string expectedResponse = "{'ID':2,'Name':'New Item'}".Replace("'", "\"");
HttpClient httpClient = new HttpClient();
SampleItem msgContent = new SampleItem {Name = "New Item"};
HttpRequestMessage<SampleItem> request = new HttpRequestMessage<SampleItem>(msgContent, new MediaTypeHeaderValue("application/json"));
request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
HttpResponseMessage response = httpClient.SendAsync(request).Result;
Assert.IsNotNull(response.Content);
Assert.IsNotNull(response.Content.Headers.ContentType);
Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType);
Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result);
}
}
public class SampleItemsController : ApiController
{
public SampleItem Post(SampleItem newItem)
{
newItem.ID = 2;
return newItem;
}
public SampleItem Get()
{
return new SampleItem
{
ID = 1,
Name = "Saved Item"
};
}
}
public class SampleItem
{
public int ID { get; set; }
public string Name { get; set; }
}
The reason I am doing is because I want to test that my routes and my custom formatter are configured correctly and are working properly. This example doesn't use my routes or my custom formatter, but I figure if I can get this test to pass I can use that
knowledge to write a real test. Right now I can test that my routes and my formatter works using Fiddler, but that is not automated.
I figured this out. I think HttpClient is built to take and object and serialize it using a formatter you configure when calling HttpClient. Since I was passing in a string and giving a format of application/json it was trying to reformat my already serialized
string using the json formatter. To workaround this I just wrote a Formatter that does nothing but pass my string through.
x97mdr
Member
5 Points
3 Posts
Hosting the server for testing
Feb 23, 2012 12:43 AM|LINK
Hey all,
I noticed the nice self-hosting option of the WebAPI. How can I utilize this in my unit/integration tests to ensure that if I hit a particular URL with a particular verb and a particular media type that I get back what I expect?
I tried setting up a server in a base class of my test assembly like so:
public class BaseTest { protected HttpSelfHostServer Server { get; set; } [SetUp] public void SetUp() { var config = new HttpSelfHostConfiguration("http://localhost:8080"); config.Routes.MapHttpRoute( "API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); Server = new HttpSelfHostServer(config); Server.OpenAsync().Wait(); } [TearDown] public void TearDown() { Server.CloseAsync(); Server.Dispose(); } }When I try calling to the server like so:
[TestFixture] public class WhenWorkingWithProducts : BaseTest { [Test] public void ThenShouldGetAllProductsSuccessfully() { var request = new HttpRequestMessage(HttpMethod.Get, @"http://localhost:8080/api/products/"); CancellationToken cancellationToken = new CancellationToken(); var response = Server.SubmitRequestAsync(request, cancellationToken); response.Wait(); var result = response.Result; result.StatusCode.ShouldEqual(System.Net.HttpStatusCode.OK); } }I get back a 404 instead of a 200. However, when I hit the same URL by debugging the project i get back what I expect. What am I missing here? :)
Thanks in advance
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Hosting the server for testing
Feb 23, 2012 02:45 AM|LINK
From your code, I see that you are mixing couple of things:
1. You could use HttpSelfHostServer entirely without any network communication to submit messages up the service stack using the SubmitRequestAsync(HttpRequestMessage, CancellationToken).
2. You can use the HttpClient -> ...content on network....-> HttpSelfHostServer -> message handlers->Dispatcher->..rest of the stack.
I would prefer option 2. above. A sample standalone test written in XUnit is below (I hope you can relate this to the test framework that you are using).
using System; using System.Net.Http; using System.Net.Http.Headers; using System.Web.Http; using System.Web.Http.SelfHost; using Xunit; public class SampleTests : IDisposable { [Fact] public void PostTest() { string expectedResponse = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace("'", "\""); HttpClient httpClient = new HttpClient(); HttpRequestMessage<string> request = new HttpRequestMessage<string>("Hello", new MediaTypeHeaderValue("application/xml")); request.RequestUri = new Uri(this.baseAddress + "/api/SampleTests/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.NotNull(response.Content); Assert.NotNull(response.Content.Headers.ContentType); Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType); Assert.Equal<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); } [Fact] public void GetTest() { string expectedResponse = "<?xml version='1.0' encoding='utf-8'?><string>Hello</string>".Replace("'", "\""); HttpClient httpClient = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(); request.RequestUri = new Uri(this.baseAddress + "/api/SampleTests/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); request.Method = HttpMethod.Get; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.NotNull(response.Content); Assert.NotNull(response.Content.Headers.ContentType); Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType); Assert.Equal<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); } private HttpSelfHostServer server = null; private string baseAddress = null; // Setup public SampleTests() { baseAddress = string.Format("http://{0}:9090", Environment.MachineName); HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.baseAddress); config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); server = new HttpSelfHostServer(config); server.OpenAsync().Wait(); } //tear down public void Dispose() { if (server != null) { server.CloseAsync().Wait(); } } } public class SampleTestsController : ApiController { public string Post(string input) { return input; } public string Get() { return "Hello"; } }Kiran Challa
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Hosting the server for testing
Feb 23, 2012 03:15 AM|LINK
If you are curious as to writing a test using the option 1. above, you could it like the following:
[Fact] public void TestNotUsingNetwork() { HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.baseAddress); config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); HttpSelfHostServer server = new HttpSelfHostServer(config); HttpRequestMessage<string> request = new HttpRequestMessage<string>("Hello", new MediaTypeHeaderValue("application/xml")); request.RequestUri = new Uri(this.baseAddress + "/api/SampleTests/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); request.Method = HttpMethod.Post; HttpResponseMessage response = server.SubmitRequestAsync(request, new CancellationToken()).Result; Assert.NotNull(response.Content); Assert.NotNull(response.Content.Headers.ContentType); Assert.Equal<string>("application/xml", response.Content.Headers.ContentType.MediaType); Assert.Equal<string>("Hello", response.Content.ReadAsAsync<string>().Result); }Kiran Challa
x97mdr
Member
5 Points
3 Posts
Re: Hosting the server for testing
Feb 23, 2012 10:19 PM|LINK
I may have been a little bit hasty marking this as answered.
In the example code you have, the controller is directly in the test project. However, in real situations this isn't the case. In my example my controller is in another assembly (project in this case).
In this situation I cannot seem to get the test to hit the proper controller.
Any ideas?
Kiran Challa
Participant
1460 Points
285 Posts
Microsoft
Re: Hosting the server for testing
Feb 23, 2012 10:46 PM|LINK
Actually we currently have a bug regarding this where Controllers present in separate dlls are not found if those dlls are not loaded into memory yet...this bug is in SelfHost scenario only...but there is a workaround you could probably use:
So, assuming that "SampleTestsController" is in another dll...you could add the following line in your test, which will force the Dll to be loaded into the memory:
Type sampleTestsControllerType = typeof(OtherLibrary.SampleTestsController);
hope this helps...
Kiran Challa
x97mdr
Member
5 Points
3 Posts
Re: Hosting the server for testing
Feb 24, 2012 01:42 AM|LINK
That worked like a charm!
Thanks
jamesjbigler
Member
9 Points
5 Posts
Re: Hosting the server for testing
Mar 02, 2012 01:27 PM|LINK
Is it possible to pass in a json string as the content of your Request and have the server parse it and convert it to the Type that your Post method requires?
Here is a code example that explains what I am trying to say. The PostAsString test fails and the PostAsClass test passes. I want to know how to get the PostAsString test to pass.
[TestClass] public class SampleTests { private HttpSelfHostServer server = null; private string baseAddress = null; //Use TestInitialize to run code before running each test [TestInitialize()] public void MyTestInitialize() { baseAddress = string.Format("http://{0}:9090", Environment.MachineName); HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(this.baseAddress); config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); server = new HttpSelfHostServer(config); server.OpenAsync().Wait(); } //Use TestCleanup to run code after each test has run [TestCleanup()] public void MyTestCleanup() { if (server != null) { server.CloseAsync().Wait(); } } [TestMethod] public void GetTest() { string expectedResponse = "{'ID':1,'Name':'Saved Item'}".Replace("'", "\""); HttpClient httpClient = new HttpClient(); HttpRequestMessage request = new HttpRequestMessage(); request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Method = HttpMethod.Get; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content.Headers.ContentType); Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType); Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); } [TestMethod] public void PostAsString() { string expectedResponse = "{'ID':2,'Name':'New Item'}".Replace("'", "\""); HttpClient httpClient = new HttpClient(); string msgContent = "{'Name':'New Item'}".Replace("'", "\""); HttpRequestMessage<string> request = new HttpRequestMessage<string>(msgContent, new MediaTypeHeaderValue("application/json")); request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content.Headers.ContentType); Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType); Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); } [TestMethod] public void PostAsClass() { string expectedResponse = "{'ID':2,'Name':'New Item'}".Replace("'", "\""); HttpClient httpClient = new HttpClient(); SampleItem msgContent = new SampleItem {Name = "New Item"}; HttpRequestMessage<SampleItem> request = new HttpRequestMessage<SampleItem>(msgContent, new MediaTypeHeaderValue("application/json")); request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content.Headers.ContentType); Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType); Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); } } public class SampleItemsController : ApiController { public SampleItem Post(SampleItem newItem) { newItem.ID = 2; return newItem; } public SampleItem Get() { return new SampleItem { ID = 1, Name = "Saved Item" }; } } public class SampleItem { public int ID { get; set; } public string Name { get; set; } }The reason I am doing is because I want to test that my routes and my custom formatter are configured correctly and are working properly. This example doesn't use my routes or my custom formatter, but I figure if I can get this test to pass I can use that knowledge to write a real test. Right now I can test that my routes and my formatter works using Fiddler, but that is not automated.
Thanks
James
jamesjbigler
Member
9 Points
5 Posts
Re: Hosting the server for testing
Mar 09, 2012 07:12 PM|LINK
I figured this out. I think HttpClient is built to take and object and serialize it using a formatter you configure when calling HttpClient. Since I was passing in a string and giving a format of application/json it was trying to reformat my already serialized string using the json formatter. To workaround this I just wrote a Formatter that does nothing but pass my string through.
public class NoOperationFormatter : MediaTypeFormatter { public NoOperationFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); Encoding = new UTF8Encoding(false, true); } protected override bool CanReadType(Type type) { if (type == typeof(IKeyValueModel)) { return false; } return true; } protected override bool CanWriteType(Type type) { return true; } protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { return Task.Factory.StartNew(() => { using (StreamReader streamReader = new StreamReader(stream, Encoding)) { string returnVal = streamReader.ReadToEnd(); return (object)returnVal; } }); } protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { return Task.Factory.StartNew(() => { StreamWriter writer = new StreamWriter(stream, Encoding); writer.Write((string)value); writer.Flush(); }); } }Then I just attached my formatter to my message before calling it.
[TestMethod] public void PostAsStringTest() { string expectedResponse = "{'ID':2,'Name':'New Item'}".Replace("'", "\""); HttpClient httpClient = new HttpClient(); string msgContent = "{'Name':'New Item'}".Replace("'", "\""); HttpRequestMessage<string> request = new HttpRequestMessage<string>(msgContent, new MediaTypeHeaderValue("application/json"), new MediaTypeFormatter[] { new NoOperationFormatter() }); request.RequestUri = new Uri(this.baseAddress + "/api/SampleItems/"); request.Method = HttpMethod.Post; HttpResponseMessage response = httpClient.SendAsync(request).Result; Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content.Headers.ContentType); Assert.AreEqual<string>("application/json", response.Content.Headers.ContentType.MediaType); Assert.AreEqual<string>(expectedResponse, response.Content.ReadAsStringAsync().Result); }There is a probably an easier way to do this but this works.
raghuramn
Member
248 Points
64 Posts
Microsoft
Re: Hosting the server for testing
Mar 10, 2012 01:18 AM|LINK
you dont need the dummy formatter. You have to use the non-generic HttpResponseMessage instead.
HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(baseAddress + "/api/SampleItems/"), Method = HttpMethod.Post, Content = new StringContent(msgContent) }; request.Content.Headers.ContentType = "application/json";jamesjbigler
Member
9 Points
5 Posts
Re: Hosting the server for testing
Mar 12, 2012 02:09 PM|LINK
Thanks this worked. I tried the non-generic version before, but I was missing the line to set the content type.