如何测试具有私有方法依赖性的 public 函数?

How do I test a public function that has private method dependency?

我正在尝试测试使用 HttpClient 访问外部 API 的 public 方法(方法 A)。这个public方法调用同class的一个私有方法(方法B)来获取方法A的HttpClient发送请求所需要的Access Token。我遇到的问题是我正在创建 HttpClientFactory 接口的模拟以测试方法 A 的响应,但是为了让方法 B 获得令牌,它需要它自己的 HttpClient 实例。因此,在 Test 方法中创建的模拟实例也将被方法 B 使用,并且它会尝试获取访问令牌失败。下面的代码让场景更加清晰。

待测方法(方法A):

 public async Task<HttpResponseMessage> SendAsync(string requestUri, string siteName, int accountId)
{
  try
  {
    var accessToken = await GetTokenAsync(siteName, accountId);
    if (accessToken == null)
      throw new ArgumentNullException("Error Sending request - Could not find an access token");

    var request = new HttpRequestMessage(HttpMethod.Get, $"{accessToken.Api}{requestUri}");

    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Accesstoken);

    var httpClient = _httpClientFactory.CreateClient();
    return await httpClient.SendAsync(request);
  }
  catch (Exception e)
  {
    throw new Exception("Error Sending request.", e);
  }
}

测试方法:

[Fact]
public async Task ShouldReturnHttpResponseMessage_OnSendAsync()
{

  //_jaClientMock.Setup(x => x.GetTokenAsync(It.IsAny<string>(), It.IsAny<int>())).Verifiable();

  _appSettingsMock.Setup(x => x.Value)
    .Returns(GetValidFakeAppSettings());

  HttpResponseMessage expectedResponse = GetListOfContacts(HttpStatusCode.OK, false);
  _httpClientFactoryMock.Setup(x => x.CreateClient())
    .Returns(GetMockedHttpClient(expectedResponse));

  var response = await _jaClient.SendAsync("someurl", "siteName", 1000);

  response.IsSuccessStatusCode.ShouldBeTrue();
}

私有方法(方法B):

 private async Task<AccessToken> GetTokenAsync(string siteName, int accountId)
{
  try
  {
    if (_cache.TryGetValue(GetCacheKeyForToken(siteName, accountId), out AccessToken value))
      return value;
    ....

    var httpClient = _httpClientFactory.CreateClient();
    var response = await httpClient.SendAsync(request);

    if (response.IsSuccessStatusCode)
    {
      accessToken = await response.Content.ReadAsAsync<AccessToken>();
    }

    .....  
    return accessToken;
  }
  catch (Exception e)
  {
    throw new Exception("Error Getting an Access Token.", e);
  }
}

知道如何测试方法 A 吗?

There ain't no such thing as a free lunch - 如果要对一些具有外部依赖项的代码进行单元测试,那么 必须模拟每个外部依赖项.

或者可以进一步 test pyramid 进行集成测试(虽然我们的情况可能不是这样)。

因此,您可以:

  1. 要么在 _httpClientFactory 中模拟令牌响应,就像在 SendAsync 中模拟它一样(..._httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(GetMockedHttpClient(expectedResponse));...

  2. 或者以不直接从 API 检索标记的方式重新组织代码 - 创建一些更容易模拟的单一方法 ITokenProvider 接口。

    public interface ITokenProvider
    {
        public async Task<AccessToken> GetTokenAsync(string siteName, int accountId);
    }
    ...
    public async Task<HttpResponseMessage> SendAsync(string requestUri, string siteName, int accountId)
    {
      try
      {
        var accessToken = await _tokenProvider.GetTokenAsync(siteName, accountId);
    ...
    [Fact]
    public async Task ShouldReturnHttpResponseMessage_OnSendAsync()
    {
        var tokenProviderMock = new Mock<ITokenProvider>()
            .Setup(o => o.GetTokenAsync("siteName", 1000))
            .Returns(Constants.AllowedToken);
        _jaClient = new JaClient(tokenProviderMock.Object);...