我正在尝试对外部 API 进行一些集成测试。我在网上找到的大多数指南都是关于测试 ASP.NET web api,但关于外部 API 的内容并不多。我想在这个 API 上测试一个 GET 请求,并通过检查状态代码是否正常来确认它是否通过。但是这个测试没有通过,我想知道我是否正确地做这件事。目前它给我一个状态代码 404(未找到)。
我将 xUnit
与 Microsoft.AspNetCore.TestHost
一起使用,您建议我如何测试外部 API?
private readonly HttpClient _client;
public DevicesApiTests()
{
var server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseStartup<Startup>());
_client = server.CreateClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method)
{
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
编辑
我尝试测试的 API 调用如下所示,并且工作正常
private readonly DbContext _dbContext;
private readonly IDevicesRepository _devicesRepository;
public DevicesAPIController(DbContext dbContext, IDevicesRepository devicesRepository)
{
_dbContext = dbContext;
_devicesRepository = devicesRepository;
}
[HttpPost("PostLiveDevicesToDatabase")]
public async Task<IActionResult> PostLiveDevicesToDatabase()
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
using (var response = await httpClient
.GetAsync(
"https://prtg.nl/api/content=Group,Device,Status")
)
{
string apiResponse = await response.Content.ReadAsStringAsync();
var dataDeserialized = JsonConvert.DeserializeObject<Devices>(apiResponse);
devicesList.AddRange(dataDeserialized.devices);
foreach (DevicesData device in devicesList)
{
_dbContext.Devices.Add(device);
devicesAdded.Add(device);
_dbContext.SaveChanges();
}
}
}
}
catch
{
return BadRequest();
}
}
答案 0 :(得分:1)
测试服务器的基址是localhost。 TestServer
用于内存集成测试。通过 TestServer.CreateClient()
创建的客户端将创建一个 HttpClient
实例,该实例使用内部消息处理程序来管理特定于您的 API 的请求。
如果您尝试通过调用测试服务器来访问外部 URL。您将按照设计获得 404。
如果 https://prtg.nl/api/content
不是您的 API 的本地链接,而是您要访问的实际外部链接,则使用独立的 HttpClient
//...
private static readonly HttpClient _client;
static DevicesApiTests() {
_client = new HttpClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method) {
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
//...
如果这意味着通过您的 api 端到端,那么您需要调用依赖于目标控制器和操作的本地 API 端点
答案 1 :(得分:1)
我想提出一种替代解决方案,其中涉及更改要测试的代码的设计。
当前显示的测试用例与外部 API 耦合并测试其响应 200 OK
而不是您的代码的能力(即,您的代码根本没有被引用)。这也意味着,如果无法建立到服务器的连接(例如,可能是 CI/CD 管道中的隔离构建代理,或者只是一个不稳定的咖啡馆 WIFI),则测试会由于断言之外的其他原因而失败。>
我建议将 HttpClient
及其特定于 API 的配置提取到抽象中,就像您对 IDevicesRepository
所做的一样(尽管示例中未使用它)。这允许您替换来自 API 的响应并仅测试您的代码。替换可以探索边缘情况,例如连接断开、空响应、格式错误的响应、外部服务器错误等。这样您就可以在代码中使用更多的故障路径,并使测试与外部 API 分离。
抽象的实际替换将在测试的“安排”阶段完成。您可以为此使用 Moq NuGet 包。
更新
要提供使用 Moq 模拟空 API 响应的示例,请考虑一个假设的抽象,例如:
public interface IDeviceLoader
{
public IEnumerable<DeviceDto> Get();
}
public class DeviceDto
{
// Properties here...
}
请记住,示例抽象不是异步的,当您调用 I/O(即网络)时,这可以被视为最佳实践。我跳过它以保持简单。有关如何处理异步方法的信息,请参阅 Moq documentation。
为了模拟响应,测试用例的主体可以是:
[Fact]
public async Task CheckEndpointHandlesEmptyApiResponse()
{
// How you get access to the database context and device repository is up to you.
var dbContext = ...
var deviceRepository = ...
//Arrange
var apiMock = new Mock<IDeviceLoader>();
apiMock.Setup(loader => loader.Get()).Returns(Enumerable.Empty<DeviceDto>());
var controller = new DevicesAPIController(dbContext, deviceRepository, apiMock.Object);
//Act
var actionResponse = controller.PostLiveDevicesToDatabase();
// Assert
// Check the expected HTTP result here...
}
请查看其存储库(上面链接)中的 Moq 文档以获取更多示例。