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