C# 如何使用ASP.NET内核在外部API上进行集成测试

C# 如何使用ASP.NET内核在外部API上进行集成测试,c#,asp.net-core,integration-testing,xunit,C#,Asp.net Core,Integration Testing,Xunit,我正在尝试对外部API进行一些集成测试。我在网上找到的大多数指南都是关于测试ASP.NET web api的,但是关于外部api的内容并不多。我想在此API上测试GET请求,并通过检查状态代码是否正常来确认它是否通过。然而,这个测试没有通过,我想知道我是否做得正确。目前它给了我一个状态码404(未找到) 我正在使用xUnit和Microsoft.AspNetCore.TestHost一起使用外部API,您建议我如何测试 private readonly HttpClient _client;

我正在尝试对外部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);
}
私有只读HttpClient\u客户端;
公共设备API测试()
{
var server=newtestserver(newwebhostbuilder()
.环境(“发展”)
.UseStartup());
_client=server.CreateClient();
}
[理论]
[InlineData(“获取”)]
公共异步任务GetAllDevicesFromPRTG(字符串方法)
{
//安排
var请求=新的HttpRequestMessage(新的HttpMethod(方法),”https://prtg.nl/api/content=Group,设备、状态);
//表演
var response=wait_client.SendAsync(请求);
//断言
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();
    }
}
private readonly DbContext\u DbContext;
专用只读IDevicesRepository\u设备存储库;
公用设备APIController(DbContext DbContext,IDEVICES存储设备存储库)
{
_dbContext=dbContext;
_DeviceRepository=DeviceRepository;
}
[HttpPost(“PostLiveDevicesToDatabase”)]
公共异步任务PostLiveDevicesToDatabase()
{
尝试
{
使用(var httpClient=new httpClient())
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
新的MediaTypeWithQualityHeaderValue(“应用程序/json”);
使用(var response=wait-httpClient
.GetAsync(
"https://prtg.nl/api/content=Group(设备、状态)
)
{
字符串apiResponse=wait response.Content.ReadAsStringAsync();
var dataDeserialized=JsonConvert.DeserializeObject(apiResponse);
DeviceList.AddRange(dataDeserialized.devices);
foreach(设备列表中的设备数据设备)
{
_dbContext.Devices.Add(设备);
添加设备。添加(设备);
_dbContext.SaveChanges();
}
}
}
}
抓住
{
返回请求();
}
}

测试服务器的基址是localhost<代码>测试服务器用于内存集成测试。通过
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端点,该端点取决于目标控制器和操作。测试服务器的基址为localhost<代码>测试服务器用于内存集成测试。通过
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端点,该端点取决于目标控制器和操作

我想提出一个替代解决方案,其中包括更改待测试代码的设计

当前显示的测试用例耦合到外部API,测试其响应
200OK
而不是您的代码(即,您的代码根本没有被引用)的能力。这也意味着,如果无法建立到服务器的连接(例如,可能是CI/CD管道中的独立构建代理,或者只是一个脆弱的caféWIFI),则测试失败的原因与所断言的原因不同

我建议将
HttpClient
及其特定于API的配置提取到一个抽象中,就像您使用
IDevicesRepository
所做的那样(尽管示例中没有使用它)。这允许您替换来自API的响应,并且只测试代码。替换可以探索边缘情况,例如连接断开、响应为空、响应格式错误、外部服务器错误等。这样,您可以在代码中使用更多故障路径,并保持测试与外部API的解耦

抽象的实际替换将在测试的“安排”阶段完成。您可以使用NuGet包来实现此目的

更新

提供一个使用MOQ来模拟空API响应的例子,考虑一个假设的抽象,如:

public interface IDeviceLoader
{
    public IEnumerable<DeviceDto> Get();
}

public class DeviceDto
{
    // Properties here...
}
公共接口IDeviceLoader
{
公共IEnumerable Get();
}
公共类设备到
{
//这里的属性。。。
}
请记住,示例抽象不是异步的,在调用I/O(即网络)时,可以将其视为最佳实践。为了保持简单,我跳过了它。有关如何处理异步方法的信息,请参见

为了模拟响应,测试用例的主体