C# 如何测试具有私有方法依赖性的公共函数?

C# 如何测试具有私有方法依赖性的公共函数?,c#,.net-core,xunit,httpclientfactory,C#,.net Core,Xunit,Httpclientfactory,我正在尝试测试一个公共方法(方法a),该方法使用HttpClient访问外部API。此公共方法调用同一类的私有方法(方法B),以获取方法a的HttpClient发送请求所需的访问令牌。我遇到的问题是,我正在创建HttpClientFactory接口的模拟,以测试方法a的响应,但为了方法B获得令牌,它需要自己的HttpClient实例。因此,在测试方法中创建的模拟实例也将被方法B使用,并且它将无法尝试获取访问令牌。下面的代码使场景更加清晰 待测方法(方法A): public async Task

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

待测方法(方法A):

public async Task sendaync(字符串requestUri、字符串siteName、int accountId)
{
尝试
{
var accessToken=await GetTokenAsync(siteName,accountId);
if(accessToken==null)
抛出新ArgumentNullException(“发送请求时出错-找不到访问令牌”);
var request=newhttprequestmessage(HttpMethod.Get,$“{accessToken.Api}{requestUri}”);
request.Headers.Authorization=新的AuthenticationHeaderValue(“承载者”,accessToken.accessToken);
var httpClient=_httpClientFactory.CreateClient();
返回wait-httpClient.SendAsync(请求);
}
捕获(例外e)
{
抛出新异常(“发送请求时出错。”,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();
}
[事实]
公共异步任务应返回HttpResponseMessage_OnSendAsync()
{
//_Setup(x=>x.GetTokenAsync(It.IsAny(),It.IsAny()).Verifiable();
_appSettingsMock.Setup(x=>x.Value)
.Returns(GetValidFakeAppSettings());
HttpResponseMessageExpectedResponse=GetListOfContacts(HttpStatusCode.OK,false);
_httpClientFactoryMock.Setup(x=>x.CreateClient())
.返回(GetMockedHttpClient(expectedResponse));
var response=await _jaClient.SendAsync(“someurl”,“siteName”,1000);
response.IsSuccessStatusCode.ShouldBeTrue();
}
私有方法(方法B):

private异步任务GetTokenAsync(字符串siteName,int accountId)
{
尝试
{
if(_cache.TryGetValue(GetCacheKeyForToken(siteName,accountId),out AccessToken值))
返回值;
....
var httpClient=_httpClientFactory.CreateClient();
var response=wait httpClient.sendaync(请求);
if(响应。IsSuccessStatusCode)
{
accessToken=wait response.Content.ReadAsAsync();
}
.....  
返回accessToken;
}
捕获(例外e)
{
抛出新异常(“获取访问令牌时出错。”,e);
}
}
你知道如何测试方法A吗?

-如果你想用外部依赖项对一些代码进行单元测试,那么必须模拟这些外部依赖项中的每一个

或者,可以进一步进行集成测试(尽管这可能不是我们的情况)

因此,你可以:

  • \u httpClientFactory
    中模拟令牌响应的方式与在
    SendAsync
    中模拟令牌响应的方式相同(
    ..\u httpClientFactoryMock.Setup(x=>x.CreateClient())。返回(GetMockedHttpClient(expectedResponse));…

  • 或者以不直接从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);...
    
    公共接口提供程序
    {
    公共异步任务GetTokenAsync(字符串siteName,int accountId);
    }
    ...
    公共异步任务SendAsync(字符串requestUri、字符串siteName、int accountId)
    {
    尝试
    {
    var accessToken=await\u tokenProvider.GetTokenAsync(siteName,accountId);
    ...
    [事实]
    公共异步任务应返回HttpResponseMessage_OnSendAsync()
    {
    var tokenProviderMock=new Mock()
    .Setup(o=>o.GetTokenAsync(“siteName”,1000))
    .Returns(常量.AllowedToken);
    _jaClient=新的jaClient(tokenProviderMock.Object);。。。
    

  • 这看起来像是一个与实现问题紧密耦合的案例,这使得隔离测试变得困难。您所经历的麻烦应该被视为代码需要重构的迹象。请回顾您当前的设计。@Nkosi,我理解。我试图避免重构代码来对其进行单元测试。因此从您的角度来看,不存在任何问题测试方法A的方法?不,这不是我的意思。你可以测试它,只是不容易。注意,我说的很难,不是不可能。你的问题很可能是如何设置HttpClient的使用。但是因为没有显示代码,所以无法评估。对。是的,我决定重构,提取私有方法并通过其公共接口进行测试。做得好。很高兴你找到了解决方案。愉快的编码。抱歉。对于选项2:我不明白你所说的“模仿SendAsync的方式”是什么意思。我不是在模拟SendAsync,这实际上是我想要测试的方法。我是在模拟我正在测试的SendAsync方法的_httpClientFactory的响应,但是我怎么能有两个_httpClientFactory的模拟呢?一个用于SendAsync,一个用于GetTokenAsync。@D.B Typo-意思是“用于
    SendAsync
    ”请你举一个更明确的例子,谢谢。
     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);
      }
    }
    
    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);...