Я новичок ie до 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. Должен позвонить в службу провайдера токена и получить токен 2. С помощью токена аутентифицируйте службу, чтобы добавить сущность
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
, так как не удалось десериализовать responseStream.Result
в приватном методе GetAccessTokenDetailsAsync()
, в AccessTokenDetails
в этом модульном тесте. Десериализация не удалась, так как значение responseStream.Result
является просто строкой GUID.
Может кто-нибудь сказать мне, что я сталкиваюсь с проблемой "инверсии зависимости", и предложить мне способ ее преодоления?
Я думаю о том, чтобы отделить GetAccessTokenDetailsAsync
от другого класса, что-то вроде AccessTokenProvider
, и высмеивать его, чтобы это произошло - будет ли это хорошим подходом? что может быть лучшим подходом для решения этой проблемы.