使用 xUnit 测试控制器

Testing controller with xUnit

我有 webapi,它需要调用其他端点并获取数据。

我目前的代码如下

//http客户端实现

public interface IHttpClientFactory
{
    HttpClient Create();
}

public class HttpClientFactory : IHttpClientFactory
{
    private readonly ApplicationSettings _applicationSettings;
    HttpClient _httpClient;

    public HttpClientFactory(IOptions<ApplicationSettings> settings)
    {
        _applicationSettings = settings.Value;
    }
    public HttpClient Create()
    {
        if (_httpClient != null)
            return _httpClient;

        var client = new HttpClient()
        {
            BaseAddress = new Uri($"{_applicationSettings.BaseUrl}")
        };
        _httpClient = client;
        return _httpClient;
    }
}

 public interface IGetItemsQuery
{
    Task<IEnumerable<T>> Execute<T>(string url);
}

public class GetItemQuery: IGetItemsQuery
{
    private readonly IHttpClientFactory _httpClientFactory;
    public GetPhotosQuery(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<IEnumerable<T>> Execute<T>(string url)
    {
        using (var response = await _httpClientFactory.Create().GetAsync($"{url}").ConfigureAwait(false))
        {
            response.EnsureSuccessStatusCode();
            var resp = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            var items = JArray.Parse(resp);
            return items.ToObject<T[]>();
        }
    }

在我的控制器部分

   private readonly IGetItemsQuery _getItemsQuery;

       public HomeController(IGetItemsQuery getItemsQuery)
    {
        _getItemsQuery = getItemsQuery;
    }

 appsettings

     "ApplicationSettings": {
      "BaseUrl": "http://someendpoint.com/"

}

启动

   services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
        services.AddScoped<IGetItemsQuery, GetPhotosQuery>();
        services.AddScoped<IHttpClientFactory, HttpClientFactory>();

我想在我的测试中尝试类似下面的东西

     [Fact]
    public void Test_Index()
    {
        // Arrange
        var itemsQuery = new Mock<IGetItemsQuery>();
        var controller = new HomeController(itemsQuery.Object);

        // Act
        var result = controller.Index();

        // Assert
        var viewResult = Assert.IsType<ViewResult>(result);
        Assert.Null(viewResult.ViewName);
    }

这是在创建模拟 IGetItemsQuery,但这并不是在模拟实际的 IHttpClientFactory。

有没有办法做到这一点

根据您使用抽象依赖项的设计,无需模拟客户端工厂即可对控制器进行单元测试。

正如您在测试中所做的那样,您模拟了 IGetItemsQuery,但您没有将其设置为在测试中调用时按预期运行。

例如,如果被测控制器方法看起来像这样

private readonly IGetItemsQuery getItemsQuery;

public HomeController(IGetItemsQuery getItemsQuery) {
    this.getItemsQuery = getItemsQuery;
}

public async Task<IActionResult> Index() {
    var url = "....";

    var items = await getItemsQuery.Execute<MyItem>(url);

    return View(items);
}

然后作为被测方法的 Index 操作的独立单元测试可能类似于

[Fact]
public async Task Index_Should_Return_View_With_Items() {
    // Arrange
    var itemsQuery = new Mock<IGetItemsQuery>();

    var items = new MyItem[] {
        new MyItem(),
        new MyItem()
    };

    itemsQuery.Setup(_ => _.Execute<MyItem>(It.IsAny<string>()))
        .ReturnsAsync(items);

    var controller = new HomeController(itemsQuery.Object);

    // Act
    var result = await controller.Index();

    // Assert        
    var viewResult = Assert.IsType<ViewResult>(result);
    Assert.Null(viewResult.ViewName);
}