Automatic testing of ApiController - c#

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..

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.

Test all endpoints for authentication, .Net core 3.0

I want to build integration tests to make sure all of our endpoints are locked behind authentication.
I will then fetch all of our endpoints from our swagger.
How can i await this call and then use this data as memberData or classData? Since it's async.
Should i use fixtures, or some kind of ----Data?
[Collection("A collection")]
public class EndpointsTests
{
RouteDataFixture fixture;
public EndpointsTests(RouteDataFixture fixture)
{
this.fixture = fixture;
}
[Theory]
[ClassData(typeof(SomeClassWithAsyncConstructor))]
public async Task Test_This(string path, string method)
{
//test the awaited data from class
if(method == "GET"=
var response = await fixture.GetAsync(path)
//Check if the response is unauthorized since we didn't send a token
}
}
If there is no special reason you can use unit test instead of integration test and you can check your endpoints defined or not defined any authorization attribute. For example;
public class SomeController : Controller
{
[AllowAnonymous]
public IAsyncResult Post1()
{
// codes...
}
[Authorize("some_permission")]
public IAsyncResult Post2()
{
// codes...
}
public IAsyncResult Post3()
{
// codes...
}
}
this is your controller class.
[Fact]
public void Test()
{
var _endpoints = new List<(Type, MethodInfo)>(); // All endpoints in my project
var asm = Assembly.Load("MyAssembly");
var cType = typeof(Controller);
var types = asm.GetTypes().Where(x => x.IsSubclassOf(cType)).ToList();
foreach (Type t in types)
{
var mInfos = t.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.DeclaringType.Equals(t)).ToList();
foreach (MethodInfo mInfo in mInfos)
_endpoints.Add((t, mInfo));
}
var nonAuthEndPoints = _endpoints.Where(x => !x.IsDefined(typeof(AuthorizeAttribute)) && !x.IsDefined(typeof(AllowAnonymousAttribute)));
nonAuthEndPoints.Should().BeEmpty();
}
and this is your test method. This would check all endpoints and force they should have AllowAnonymous or Authorize.
In this example your Post1 and Post2 endpoints passed the test but post3 failed.

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.

Routes not mapping correctly when creating in memory web api server

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);
}
}
}

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