如何测试具有私有方法依赖性的 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 进行集成测试(虽然我们的情况可能不是这样)。
因此,您可以:
要么在 _httpClientFactory
中模拟令牌响应,就像在 SendAsync
中模拟它一样(..._httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(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);...
我正在尝试测试使用 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 进行集成测试(虽然我们的情况可能不是这样)。
因此,您可以:
要么在
_httpClientFactory
中模拟令牌响应,就像在SendAsync
中模拟它一样(..._httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(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);...