Routes not mapping correctly when creating in memory web api server - c#

When I curl to the /test route it works fine, however the test below 404's when trying to hit the in memory server on the same route.
When inspecting _client and _config appear to be ok - although I am not sure how to confirm that my in memory server is functioning correctly.
Does anybody know how I can get my in memory web server to map it's routes correctly so my test method can reach it?
namespace Robo.Tests.Controllers
{
[TestClass]
public class IntegrationTests
{
private HttpMessageInvoker _client;
private HttpConfiguration _config = new HttpConfiguration();
[TestInitialize]
public void SetupTest()
{
_config.MapHttpAttributeRoutes();
_config.EnsureInitialized();
var server = new HttpServer(_config);
_client = new HttpMessageInvoker(server);
}
[TestMethod]
public async Task Test()
{
var result = await _client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://localhost/test"), CancellationToken.None);
}
}
}
and controller in case you are interested
namespace Robo.Controllers
{
//[ValidationActionFilter]
public class CVController : ApiController
{
[HttpGet]
[Route("test")]
public async Task<IHttpActionResult> test()
{
return Ok();
}
}
}

For in-memory server testing The following utility class was created. It basically wraps the setup functionality in the example shown.
internal interface IHttpTestServer : IDisposable {
HttpConfiguration Configuration { get; }
HttpClient CreateClient();
}
internal class HttpTestServer : IHttpTestServer {
HttpServer httpServer;
public HttpTestServer(HttpConfiguration configuration = null) {
httpServer = new HttpServer(configuration ?? new HttpConfiguration());
}
public HttpConfiguration Configuration {
get { return httpServer.Configuration; }
}
public HttpClient CreateClient() {
var client = new HttpClient(httpServer);
return client;
}
public void Dispose() {
if (httpServer != null) {
httpServer.Dispose();
httpServer = null;
}
}
public static IHttpTestServer Create(HttpConfiguration configuration = null) {
return new HttpTestServer(configuration);
}
}
The following test was crafted to demonstrate the use of in memory server using OP
[TestClass]
public class IntegrationTests {
[TestMethod]
public async Task Test() {
using (var server = HttpTestServer.Create()) {
//Arrange
var config = server.Configuration;
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
var client = server.CreateClient();
var url = "http://localhost/test";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var expected = System.Net.HttpStatusCode.OK;
//Act
var result = await client.SendAsync(request, CancellationToken.None);
//Assert
Assert.IsNotNull(result);
Assert.AreEqual(expected, result.StatusCode);
}
}
public class CVController : ApiController {
[HttpGet]
[Route("test")]
public async Task<IHttpActionResult> test() {
return Ok();
}
}
}
Test passes.
The thing about this example is that the test and controller exist in same assembly so map attribute scans the assembly it was called in and found API controller with attribute routes. If controller lived in another project then the web API config of that project should be called on the HttpConfiguration to properly configure web API.
UPDATE
The test project and web api project should be two separate projects. That said, The web project should have a WebApiConfig.cs file with a static WebApiConfig.Register class and method. That method takes a HttpConfiguration parameter. The test should use that method to configure the api for in memory calls.
[TestClass]
public class IntegrationTests {
[TestMethod]
public async Task Test() {
using (var server = HttpTestServer.Create()) {
//Arrange
var config = server.Configuration;
//Config server
MyWebApiNamespace.WebApiConfig.Register(config);
var client = server.CreateClient();
var url = "http://localhost/test";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var expected = System.Net.HttpStatusCode.OK;
//Act
var result = await client.SendAsync(request, CancellationToken.None);
//Assert
Assert.IsNotNull(result);
Assert.AreEqual(expected, result.StatusCode);
}
}
}

Related

How to close Resharper test runner after finishing integration test in xUnit Test project (.NET Core)?

i am new to integration tests. I have an xUnit project in my solution which contains one test only.
Here's the definition of my test:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "testoweImie",
LastName = "testoweNazwisko",
MailAddress = "test#test.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
And the BaseIntegrationTest class definition:
public abstract class BaseIntegrationTest
{
private const string TestDatabaseName = "TestDatabase";
protected BaseIntegrationTest()
{
var appFactory = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
RemoveDatabaseContextFromServicesCollectionIfFound<EventStoreContext>(services);
RemoveDatabaseContextFromServicesCollectionIfFound<GrantContext>(services);
services
.AddDbContext<EventStoreContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName))
.AddDbContext<GrantContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName));
});
});
HttpClient = appFactory.CreateClient();
}
protected HttpClient HttpClient { get; }
protected static StringContent SerializeObject(object #object) =>
new StringContent(
JsonConvert.SerializeObject(#object),
Encoding.UTF8,
"application/json");
private static void RemoveDatabaseContextFromServicesCollectionIfFound<T>(IServiceCollection services)
where T : DbContext
{
var descriptor = services.SingleOrDefault(service =>
service.ServiceType == typeof(DbContextOptions<T>));
if (!(descriptor is null))
{
services
.Remove(descriptor);
}
}
}
When i run tests, it takes few seconds, and the test ends successfully. The problem is that Resharper Test Runner still runs, although i've already have collected results. what am i doing wrong here? Do i have to somehow dispose the HttpClient, after performing all tests? If so, how to achieve that? Thanks for any help.
It looks like you're actually booting the application inside the test rather than using the testhost (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1)
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Notice the IClassFixture stuff.

Tests - .net core 3

I'm creating a Rest API in .net core 3 (my first one). In that API I did a dll that I call from some API methods.
I want to write some tests on that dll but I have some issues with some dependency injection and getting values set in API ConfigureServices. My main problem is to get an HttpClient by name with a IHttpClientFactory.
My architecture is :
Project WebApi
Project dllApi
Project Tests
Here is my ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpClient("csms", c =>
{
c.BaseAddress = Configuration.GetValue<Uri>("ExternalAPI:CSMS:Url");
});
services.AddSingleton(typeof(IdllClass), typeof(dllClass));
}
My class in dll
public class dllClass
{
private readonly IHttpClientFactory ClientFactory;
public dllClass(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public async Task<Credentials> GetCredentials()
{
var request = new HttpRequestMessage(HttpMethod.Get, $"Security/GetCredentials");
using (var client = ClientFactory.CreateClient("csms"))
{
var response = await client.SendAsync(request);
}
return new Credentials();
}
}
I tried different method (moq, Substitute, ...) and the closest I got from my goal was this one below but it doesn't find the HttpClient by name :
public void GetCredentials()
{
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var service = new dllClass(httpClientFactoryMock);
var result = service.GetCredentials().Result;
}
How should I write that test ?
Thank you for your help
As the Comment states. You haven't mocked the CreateClient method. It should look something like the following:
public void GetCredentials()
{
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var csmsTestClient = new HttpClient();
httpClientFactoryMock.CreateClient("csms").Returns(csmsTestClient)
var service = new dllClass(httpClientFactoryMock);
var result = service.GetCredentials().Result;
}
Then you need to setup your HttpClient to point at whatever url you want to test.

How to test and call my api in external DLL

Using ASP.NET Core 2.0 for 1st time.
I have a web project which references a DLL that i have created.
In this DLL is a simple method...
namespace InformedWorkerApi
{
[Route("api/[controller]")]
public class RegistrationController: Controller
{
private readonly IAccountRepository _accountRepository;
public RegistrationController(IAccountRepository accountRepository)
{
_accountRepository = accountRepository;
}
[HttpPost()]
[Route("SignOn")]
public async Task<InformedWorkerModels.Account> SignOn([FromBody]SignOnRequest model)
{
return await _accountRepository.SignOn(model.EmailAddress, model.Password);
}
}
}
I have also created a test project which references my DLL...
[TestMethod]
public async Task SignOn()
{
var webHostBuilder = new WebHostBuilder()
.UseStartup<Startup>();
using (var host = new TestServer(webHostBuilder))
{
using (var client = host.CreateClient())
{
var requestData = new SignOnRequest { EmailAddress = "emailAddress", Password= "password" };
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
var response = await client.PostAsync("api/Registration/SignOn", content);
//do some asserts here
}
}
}
I get the error status code 404 not found.
What am i getting so wrong here please?
You have email and password as part of the route but send them as content in the body. that wont match the route template for the action and thus 404 Not Found.
Create a model to hold the data
public class SignOnRequest {
[Required]
public string emailAddress { get; set; }
[Required]
public string password { get; set; }
}
Also for core you have to specify with parameter attributes where the framework should look for parameters.
[Route("api/[controller]")]
public class RegistrationController : Controller{
[HttpPost()]
[Route("SignOn")] // Matches POST api/Registration/SignOn
public async Task<IActionResult> SignOn([FromBody]SignOnRequest model) {
if(ModelState.IsValid) {
var response = await _accountRepository.SignOn(model.emailAddress, model.password);
return Ok(response);
}
return BadRequest();
}
}
Which should now match the request being made in the integration test
var requestData = new { emailAddress = "emailAddress", password = "password" };
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
var response = await client.PostAsync("api/Registration/SignOn", content);
As you also mentioned that the controller is in another assembly you would need to make sure that the controller is registered with the ServiceCollection within Startup so that the framework is aware of how to resolve the controller. Update the ConfigureServices to include
services.AddMvc()
.AddApplicationPart(typeof(RegistrationController).GetTypeInfo().Assembly)
.AddControllersAsServices();
as referenced from the following answer ASP.NET Core MVC controllers in separate assembly

Automatic testing of ApiController

I have an ApiController and would like to test it with unit tests including the routing.
An example:
[RoutePrefix("prefix")]
public class Controller : ApiController
{
[HttpGet]
[Route("{id1}")]
public int Add(int id1, [FromUri] int id2)
{
return id1 + id2;
}
}
I would now like to test this method. I see, that I can test it like an ordinary method. But I would also like to test it with the translation of the URL to the method parameters.
Basically I would like to have an automatic test where I call a URL like prefix/10?id2=5 and get a result of 15. Is this somehow possible?
I wrote a little helper class for in-memory integration testing that can be called as part of the test suit.
internal interface IHttpTestServer : IDisposable {
HttpConfiguration Configuration { get; }
HttpClient CreateClient();
}
internal class HttpTestServer : IHttpTestServer {
HttpServer httpServer;
public HttpTestServer(HttpConfiguration configuration = null) {
httpServer = new HttpServer(configuration ?? new HttpConfiguration());
}
public HttpConfiguration Configuration {
get { return httpServer.Configuration; }
}
public HttpClient CreateClient() {
var client = new HttpClient(httpServer);
return client;
}
public void Dispose() {
if (httpServer != null) {
httpServer.Dispose();
httpServer = null;
}
}
public static IHttpTestServer Create(HttpConfiguration configuration = null) {
return new HttpTestServer(configuration);
}
}
And would then use it like this
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_InMemory_Hosting() {
using (var server = new HttpTestServer()) {
MyWebAPiProjectNamespace.WebApiConfig.Configure(server.Configuration);
var client = server.CreateClient();
string url = "http://localhost/prefix/10?id2=5";
var expected = 15;
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);
var result = await response.Content.ReadAsAsync<int>();
Assert.AreEqual(expected, result);
}
}
}
This will configure an in-memory test server that the test can make calls to using its httpclient. It is in essence an end-to-end integration test.
Create an OWIN StartUp class using Microsoft ASP.NET Web API 2.2 OWIN package:
public class Startup
{
public void Configuration(IAppBuilder builder)
{
var config = new HttpConfiguration();
builder.UseWebApi(config);
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
}
}
Use Microsoft ASP.NET Web API 2.2 Self Host package in your tests (used NUnit for example):
[Test]
[TestCase(10, 5, 15)]
[TestCase(1, 2, 3)]
// add your test cases
public async Task AdditionTests(int a, int b, int result)
{
// Arrange
var address = "http://localhost:5050";
using (WebApp.Start<Startup>(address))
{
var client = new HttpClient();
var requestUri = $"{address}/prefix/{a}?id2={b}";
// Act
var response = await client.GetAsync(requestUri);
// Assert
Assert.IsTrue(await response.Content.ReadAsAsync<int>() == result);
}
}
That is an integration test, not a unit test. If you wanted to automate this you would have to have a tool that would launch/host your web api and then execute requests against it.
If you wanted to keep it as a unit test though you could validate the attributes on the class and the method and check the values.
var type = typeof(Controller);
var attributeRoutePrefix = type.GetCustomAttribute(typeof(RoutePrefixAttribute)) as RoutePrefixAttribute;
Assert.IsNotNull(attributeRoutePrefix);
Assert.AreEqual("prefix", attributeRoutePrefix.Prefix);
var methodAttribute = type.GetMethod(nameof(Controller.Add)).GetCustomAttribute(typeof(RouteAttribute)) as RouteAttribute;
Assert.IsNotNull(methodAttribute);
Assert.AreEqual("id1", methodAttribute.Template);
Its possible by using postman or fiddler to test with url param..

How do I use NinjectSelfHostBootstrapper in my tests and have more than one test pass?

Here's my NUnit test code (simplified to isolate the issue):
public class MyController : ApiController
{
[HttpGet]
[Route("api/route")]
public string Route1()
{
return "route";
}
}
[TestFixture]
public class MyTestFixture
{
private NinjectSelfHostBootstrapper _bootstrapper;
[SetUp]
public void SetUp()
{
var config = new HttpSelfHostConfiguration("http://localhost:8767");
config.MapHttpAttributeRoutes();
var kernel = new StandardKernel();
this._bootstrapper = new NinjectSelfHostBootstrapper(() => kernel, config);
this._bootstrapper.Start();
}
[TearDown]
public void TearDown()
{
if (this._bootstrapper != null)
{
this._bootstrapper.Dispose();
}
}
[Test]
public void MyTest1()
{
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:8767/api/route");
var response = client.SendAsync(request).Result;
var content = response.Content.ReadAsStringAsync().Result;
Assert.That(content, Is.EqualTo("\"route\""));
}
}
[Test]
public void MyTest2()
{
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:8767/api/route");
var response = client.SendAsync(request).Result;
var content = response.Content.ReadAsStringAsync().Result;
Assert.That(content, Is.EqualTo("\"route\""));
}
}
}
When I run that using the default NinjectSelfHostBootstrapper the first test passes fine, but the second throws an exception, saying:
A registration already exists for URI 'http://localhost:8767/'.
When I examine the code for these two files:
https://github.com/ninject/Ninject.Web.WebApi/blob/master/src/Ninject.Web.WebApi.Selfhost/NinjectWebApiSelfHost.cs
https://github.com/ninject/Ninject.Web.Common/blob/master/src/Ninject.Web.Common.SelfHost/NinjectSelfHostBootstrapper.cs
I can see why - an HttpSelfHostServer is created in the latter and OpenAsync() is called on the server, but CloseAsync() isn't called on the server - is this a bug or am I doing something wrong? How do I get NinjectSelfHostBootstrapper to work with subsequently called tests?

Categories