【发布时间】:2020-04-27 08:54:14
【问题描述】:
我是 C# 和 TDD 的新手。我正在开发一个产品,我需要为一些 HTTP API 调用编写单元测试。下面是控制器的样子:
public class CommunicationController : ControllerBase
{
private readonly IHttpClientFactory _clientFactory;
private readonly AppSettings _appSettings;
public CommunicationController(IHttpClientFactory clientFactory, IOptions<AppSettings> appSettings)
{
_clientFactory = clientFactory;
_appSettings = appSettings.Value;
}
[HttpPost]
public async Task<IActionResult> PostEntity([FromBody] Entity entity)
{
if (entity.foo == null)
{
NoActionsMessage noActionsMessage = new NoActionsMessage
{
Message = "No actions performed"
};
return Ok(noActionsMessage);
}
var accessTokenDatails = await GetAccessTokenDetailsAsync();
var callUrl = "http://someUrlGoesHere";
var json = JsonConvert.SerializeObject(entity);
var content = new System.Net.Http.StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Put, new Uri(callUrl))
{
Content = content
};
request.Headers.Add("accessToken", accessTokenDatails.AccessToken);
return await InvokeHttpCall(request);
}
private async Task<AccessTokenDetails> GetAccessTokenDetailsAsync()
{
var appId = _appSettings.AppId;
var appSecret = _appSettings.AppSecret;
var refreshToken = _appSettings.RefreshToken;
var request = new HttpRequestMessage(HttpMethod.Get, new Uri("sometokenproviderUrl"));
request.Headers.Add("applicationId", appId);
request.Headers.Add("applicationSecret", appSecret);
request.Headers.Add("refreshToken", refreshToken);
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseStream = response.Content.ReadAsStringAsync();
// [ALERT] the failing line in unit test - because responseStream.Result is just a GUID and this the the problem
var result = JsonConvert.DeserializeObject<AccessTokenDetails>(responseStream.Result);
return result;
}
else
{
throw new ArgumentException("Unable to get Access Token");
}
}
}
此 POST 方法正在调用私有方法。通过使用给定的适当实体调用此 post 方法: 1.应该调用token provider服务并获取token 2.使用token,验证服务添加实体
AccessTokenDetails 类如下:
public sealed class AccessTokenDetails
{
[JsonProperty("accessToken")]
public string AccessToken { get; set; }
[JsonProperty("endpointUrl")]
public Uri EndpointUrl { get; set; }
[JsonProperty("accessTokenExpiry")]
public long AccessTokenExpiry { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
}
现在说到单元测试(我正在使用 XUnit),我有一个如下的测试方法:
public async Task Entity_Post_Should_Return_OK()
{
/ Arrange - IHttpClientFactoryHttpClientFactory
var httpClientFactory = new Mock<IHttpClientFactory>();
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
var fixture = new Fixture();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(fixture.Create<string>),
});
var client = new HttpClient(mockHttpMessageHandler.Object);
client.BaseAddress = fixture.Create<Uri>();
httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
// Arrange - IOptions
var optionsMock = new Mock<IOptions<AppSettings>>();
optionsMock.SetupGet(o => o.Value).Returns(new AppSettings
{
AppId = "mockappid",
AppSecret = "mockappsecret",
RefreshToken = "mockrefreshtoken"
});
// Arrange - Entity
AddActionEntity entity = new Entity();
entity.foo = "justfoo";
// Act
var controller = new CommunicationController(httpClientFactory.Object, optionsMock.Object);
var result = await controller.PostEntity(entity);
// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom<OkObjectResult>(result);
}
此特定测试用例在调用 PostEntity 方法时失败,因为它未能将 GetAccessTokenDetailsAsync() 私有方法中的 responseStream.Result 反序列化为此单元测试中的 AccessTokenDetails。反序列化失败,因为 responseStream.Result 的值只是一个 GUID 字符串。
谁能告诉我我遇到了“依赖倒置”问题并告诉我解决这个问题的方法吗?
我正在考虑将 GetAccessTokenDetailsAsync 分离到不同的类,例如 AccessTokenProvider 并模拟它来克服它 - 这是一个好方法吗?解决这个问题的最佳方法是什么。
【问题讨论】:
-
两件事 - 1)如果您不提供模拟服务器作为令牌源,我相信您没有删除足够的依赖项来进行单元测试,所以它可能是一个行为测试。 2)如果您的函数由于服务器返回不兼容的数据结构而失败,那么我建议测试 Fail 已经达到了它的目的并且很有帮助 - 现在要么验证服务器返回错误的信息并修复它,要么重写你的反序列化以根据返回的 guid 生成您想要的结构(如果可能的话)。
标签: c# unit-testing moq