I am working on some very simple API endpoints using Net Core 6. Along with these endpoints, I want to implement integration test for them.
For the integration tests, I am using xunit together with Microsoft.AspNetCore.Mvc.Testing , particularly WebApplicationFactory to set up the SUT.
So far, I have two API endpoints:
PROBLEM: The test for the Ping request is successful. The SUT seems to be set up correctly, the HttpClient does de request, and I get the expected result "pong". HOWEVER, when I run the test for SignIn, the SUT seems to be running correctly, but the POST request never reaches the backend. The response of the request is a 403 Forbidden, instead of the expected 401 Unauthorized.
My debugging so far:
app.UseHttpsRedirection()
. However, when the SignUp test is executed, the RequestUri is not changed to https. I think this is the cause of the 403 response, but so far I haven't been able to solve the issue .The HTTP response for PingTest
response
{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: text/plain; charset=utf-8
}}
Content: {System.Net.Http.StreamContent}
Headers: {}
IsSuccessStatusCode: true
ReasonPhrase: "OK"
RequestMessage: {Method: GET, RequestUri: 'https://localhost/api/template/ping', Version: 1.1, Content: <null>, Headers:
{
}}
StatusCode: OK
TrailingHeaders: {}
Version: {1.1}
The HTTP response for SigInTest:
response
{StatusCode: 403, ReasonPhrase: 'Forbidden', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/problem+json; charset=utf-8
}}
Content: {System.Net.Http.StreamContent}
Headers: {}
IsSuccessStatusCode: false
ReasonPhrase: "Forbidden"
RequestMessage: {Method: POST, RequestUri: 'http://localhost/api/template/sign-in', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/json; charset=utf-8
Content-Length: 35
}}
StatusCode: Forbidden
TrailingHeaders: {}
Version: {1.1}
_content: {System.Net.Http.StreamContent}
_disposed: false
_headers: {}
_reasonPhrase: null
_requestMessage: {Method: POST, RequestUri: 'http://localhost/api/template/sign-in', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/json; charset=utf-8
Content-Length: 35
}}
_statusCode: Forbidden
_trailingHeaders: {}
_version: {1.1}
I appreciate any help that you, my fellow computer guy, can offer.
My code:
Program.cs
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var services = builder.Services;
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection(); // <-- IMPORTANT
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();
app.Run();
public partial class Program { }
TemplateController.cs (API endpoints)
namespace foo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Route("api/template")]
[RequireHttps]
[ApiController]
public class TemplateController : ControllerBase
{
public TemplateController(){ }
[Route("sign-in")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public ActionResult<User> SignIn(AuthRequestDto credentials)
{
if (credentials == null) return Unauthorized("Missing credentials."); //Integration tests never reach this line. If I reach this line, I am good.
try
{
//Call to auth library
var (user, tokens) = _authService.SignIn(credentials.UserName, credentials.Password);
//Handle library's response.
return Ok();
}
catch (ArgumentException e) //<-- Thrown when auth fails. Replies with a 401, not a 403
{
return Unauthorized(e.Message);
}
catch (InvalidOperationException e)
{
return Problem(detail: e.Message, title: "Server error");
}
}
[Route("ping")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string> Ping()
{
return Ok("pong");
}
}
AuthRequestDto.cs
using System.ComponentModel.DataAnnotations;
namespace foo;
public sealed class AuthRequestDto
{
[Required]
public string UserName { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
}
TemplateControllerTest.cs (The integration tests)
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;
using Xunit;
namespace AuthWebTemplateTest.Controllers;
public class TemplateControllerTest
{
[Fact]
public async void SignInTest()
{
var app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => { });
var client = app.CreateClient(); //<-- Get the HttpClient
string userName = "foo";
string password = "bar";
var body = new JsonContent(new { userName, password }); //<-- JsonContent is a custom class. Definition is below
var resource = "/api/template/sign-in";
var response = await client.PostAsync(resource, body);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); //<-- response.StatusCode is 403 Forbidden instead
}
[Fact]
public async void PingTest()
{
var app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => { });
var client = app.CreateClient();
var response = await client.GetAsync("/api/template/ping");
Assert.Equal(HttpStatusCode.OK, response.StatusCode); //Good!
Assert.Equal("pong", await response.Content.ReadAsStringAsync()); //Also Good!
}
}
JsonContent.cs
using Newtonsoft.Json;
using System.Net.Http;
using System.Text;
namespace AuthWebTemplateTest.util;
internal class JsonContent : StringContent
{
public JsonContent(object obj)
: base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json")
{ }
}
I finally gave up and settled it by creating the httpClient with some additional conf, namely
private const string HTTPS_BASE_URL = "https://localhost";
...
var client = _factory.CreateClient(new() { BaseAddress = new Uri(HTTPS_BASE_URL) });
Now all tests work as expected.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.