C
C#•2y ago
Thinker

Integration testing a web API [Answered]

Just for the sake of the learning experience, I wanna write some integration tests for the endpoints in my web API. What's the typical way of doing this? Do you have to use some kind of HttpClient setup to call the API as if using HTTP or is there some better way of just testing the endpoints individually? For context, all of my endpoints return Task<IResult>, so I (probably) can't just test the direct return value of each endpoint method.
56 Replies
pox
pox•2y ago
Do you want to test the Http calls? Or do you just want to test the logic of the endpoint? We do both, we start up a testhost and do http calls at it. We also allow you to replace instances with mocks if you want to not have any external calls etc. You can also just create a Controller with all the dependencies and use the functions directly
Thinker
Thinker•2y ago
Test the logic of the endpoint I don't think I care about testing the HTTP calls...? I suppose I can do something like
var result = await Endpoints.GetSomethingAsync();
var ok = Assert.IsType<OkObjectResult>(result);
Assert.Equal(StatusCodes.Status200OK, ok.StatusCode);
Assert.Equal(expected, ok.Value);
var result = await Endpoints.GetSomethingAsync();
var ok = Assert.IsType<OkObjectResult>(result);
Assert.Equal(StatusCodes.Status200OK, ok.StatusCode);
Assert.Equal(expected, ok.Value);
Seems like otherwise you need to test the entire pipeline with WebApplicationFactory
pox
pox•2y ago
Yeah
Thinker
Thinker•2y ago
That's... annoying Is there at least some way to not have to write the endpoint route and instead have it be inferred from the method?
pox
pox•2y ago
We generate a client from the openapi spec that has models and routes etc, might be very overkill I've seen ppl make consts and helper methods for setting and calling the routes
Thinker
Thinker•2y ago
... you can't access Microsoft.AspNetCore.Http.Result.OkObjectResult??? why tf Why do you need to do so much stuff to write some tests catlost
pox
pox•2y ago
Most stuff you need to setup once, then the actual tests are small Are you doing the httpclient route?
Patrick
Patrick•2y ago
i don't know what you're actually trying to achieving by testing this all you seem to be doing is testing asp.net core
Thinker
Thinker•2y ago
I wanna test my endpoint methods?
Patrick
Patrick•2y ago
what's the goal of the test
pox
pox•2y ago
Test the endpoint methods
Patrick
Patrick•2y ago
that's not a goal that's an activity
Thinker
Thinker•2y ago
... testing that they do the correct thing
Starlk
Starlk•2y ago
to see if they work as expected?
Patrick
Patrick•2y ago
people writing pointless tests lead to maintaining absolutely worthless code, if you're doing this for experience learn what makes a good test
Thinker
Thinker•2y ago
wow, thanks catpog
Patrick
Patrick•2y ago
not sure if sarcasm, the point being most people end up preaching 100% code coverage and end up spamming tests which have little worth in the bigger picture of actually maintaining a project
Thinker
Thinker•2y ago
Well what should I be doing then? I wanna learn how to write tests for stuff, because that's what I've seen other people do.
pox
pox•2y ago
Agreed, doing tests for testing sake is bad
Thinker
Thinker•2y ago
I can absolutely see that
Patrick
Patrick•2y ago
that depends on the project - imitating unit tests just because you've seen them defeats the purpose of unit tests
pox
pox•2y ago
But we tend to unit test logic heavy code and do some integration tests for the "code clue"
Thinker
Thinker•2y ago
This is literally my first web API project and it's ridiculously small, really I don't need tests but I thought it'd be a good opportunity to learn anyway
Patrick
Patrick•2y ago
ok but you've built the API to do something, yes?
Thinker
Thinker•2y ago
Yes
Patrick
Patrick•2y ago
and you'd have an idea of who would consume the API?
Thinker
Thinker•2y ago
Yes
Patrick
Patrick•2y ago
the integration tests should be modelled around that - "as a user of this API, when I do X I should get Y and be able to do Z"
Thinker
Thinker•2y ago
Okay so I shouldn't be testing the individual endpoint methods but rather the HTTP routes?
Patrick
Patrick•2y ago
ive come in with a little less context to be honest, but if you're going for an integration test that sounds more like what you should be doing
Thinker
Thinker•2y ago
Right
Mayor McCheese
Mayor McCheese•2y ago
I usually do both tbh My primary motivations for web application factory tests is to test I get 401s back This can still be done with controller tests in a manner
Thinker
Thinker•2y ago
Okay so I'm getting an exception System.InvalidOperationException : The server has not been started or no web application was configured. when calling WebApplicationFactory<TEntryPoint>.CreateClient() why is this so damn hard catlost
pox
pox•2y ago
Integration tests in ASP.NET Core
Learn how integration tests ensure that an app's components function correctly at the infrastructure level, including the database, file system, and network.
Thinker
Thinker•2y ago
That's the article I'm reading Okay now it works when just using the first example of var application = new WebApplicationFactory<Program>()
pox
pox•2y ago
ingame currently so cant look 😄
Thinker
Thinker•2y ago
This is my thing currently
[Fact]
public async Task GetListsAsync_ReturnsIds() {
var ids = Enumerable.Range(0, 10)
.Select(_ => Guid.NewGuid())
.ToArray();

Mock<ITodoService> todoServiceMock = new();
todoServiceMock
.Setup(x => x.GetListIdsAsync())
.ReturnsAsync(ids);
var todoService = todoServiceMock.Object;

var a = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => builder
.ConfigureServices(services => services
.AddScoped(todoService)));
var client = a.CreateClient();

var response = await client.GetAsync("/getLists");
Assert.True(response.IsSuccessStatusCode);
var value = await response.Content.ReadFromJsonAsync<List<Guid>>();
Assert.Equal(ids, value);
}
[Fact]
public async Task GetListsAsync_ReturnsIds() {
var ids = Enumerable.Range(0, 10)
.Select(_ => Guid.NewGuid())
.ToArray();

Mock<ITodoService> todoServiceMock = new();
todoServiceMock
.Setup(x => x.GetListIdsAsync())
.ReturnsAsync(ids);
var todoService = todoServiceMock.Object;

var a = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => builder
.ConfigureServices(services => services
.AddScoped(todoService)));
var client = a.CreateClient();

var response = await client.GetAsync("/getLists");
Assert.True(response.IsSuccessStatusCode);
var value = await response.Content.ReadFromJsonAsync<List<Guid>>();
Assert.Equal(ids, value);
}
It works and I guess it's fairly compact
pox
pox•2y ago
Integration tests in ASP.NET Core
Learn how integration tests ensure that an app's components function correctly at the infrastructure level, including the database, file system, and network.
Thinker
Thinker•2y ago
How would you add a mock service to that?
pox
pox•2y ago
We use some logic so you can replace services at startup of the host
Thinker
Thinker•2y ago
Also when you say "we", do you mean your company?
pox
pox•2y ago
Yeah Swedish digital healthcare Also do you need to mock the todoSerivce? Might be worth doing a
Thinker
Thinker•2y ago
wait we're both swedes catsip again more of a case of just wanting to try what I've seen others do because I wanna learn Plus it's kind of useful since some endpoints only need to call specific methods in the service.
pox
pox•2y ago
Are you using xunit?
Thinker
Thinker•2y ago
yep
pox
pox•2y ago
If you want we could perhaps jump in to a call and look at it, maybe not tonight tho You could also do something simplier
Thinker
Thinker•2y ago
Mind showing an example?
pox
pox•2y ago
Integration tests in ASP.NET Core
Learn how integration tests ensure that an app's components function correctly at the infrastructure level, including the database, file system, and network.
Thinker
Thinker•2y ago
also completely unrelated, is your pfp literally the ica logo?
pox
pox•2y ago
Yeah
Thinker
Thinker•2y ago
love it
pox
pox•2y ago
Maybe something like this
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{

private readonly Mock<ITokenService> _mock;

public CustomWebApplicationFactory(Mock<ITokenService> mock)
{
_mock = mock;
}

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.ReplaceWithInstance(_mock.Object);
});
}
}
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{

private readonly Mock<ITokenService> _mock;

public CustomWebApplicationFactory(Mock<ITokenService> mock)
{
_mock = mock;
}

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.ReplaceWithInstance(_mock.Object);
});
}
}
Im sure there are better ways You could also just move some logic to the ctor a function to keep it simple
Mock<ITodoService> todoServiceMock = new();
var a = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => builder
.ConfigureServices(services => services
.AddScoped(todoServiceMock.Object)));
Mock<ITodoService> todoServiceMock = new();
var a = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => builder
.ConfigureServices(services => services
.AddScoped(todoServiceMock.Object)));
Do this and save the application and mock in a readonly field I gtg This got messy but 😄
Thinker
Thinker•2y ago
I need to go to sleep anyway Thanks for the assistance catsip
Mayor McCheese
Mayor McCheese•2y ago
Net6? If you’re using top level statements there’s some things you have to do for web application factory
Thinker
Thinker•2y ago
yes
Accord
Accord•2y ago
✅ This post has been marked as answered!