#c# #asp.net-core #moq #xunit
#c# #asp.net-ядро #moq #xunit
Вопрос:
У меня есть эта функция, которую я тестирую, которая называется CreateMessageHistory, у нее есть 2 зависимости, IHttpClientFactory и другой интерфейс, я смог смоделировать httpclient, но я не могу смоделировать интерфейс
public class MessageHistoryService : IMessageHistoryService
{
private const string API_MESSAGING_DB = "messages";
private const string API_GET_MESSAGE_HISTORY_DESIGN = "_design/upn-timesent";
private const string API_GET_MESSAGE_HISTORY_DESIGN_VIEW = "_view/upn-timesent-query";
private readonly ICouchDbClients couchDbClient;
private readonly IHttpClientFactory clientFactory;
public MessageHistoryService(
IHttpClientFactory clientFactory,
ICouchDbClients couchDbClient)
{
this.couchDbClient = couchDbClient ??
throw new ArgumentNullException(nameof(couchDbClient));
this.clientFactory = clientFactory ??
throw new ArgumentNullException(nameof(clientFactory));
}
public async Task CreateMessageHistory(Message message)
{
var client = this.clientFactory.CreateClient(NamedHttpClients.COUCHDB);
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
Guid id = Guid.NewGuid();
var response = await this.couchDbClient.AuthenticatedQuery(async () => {
return await client.PutAsync($"{API_MESSAGING_DB}/{id.ToString()}", message, formatter);
}, NamedHttpClients.COUCHDB, client);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
}
}
}
Кажется, у меня возникла проблема с этим интерфейсом…
public interface ICouchDbClients
{
Task<HttpResponseMessage> AuthenticatedQuery(Func<Task<HttpResponseMessage>> query, string name, HttpClient client);
}
Вот реализация этого интерфейса:
public class CouchDbClients : ICouchDbClients
{
private readonly Dictionary<string, string> authSessions;
private readonly Dictionary<string, Credentials> couchDbCredentials;
private readonly IOptions<Clients> clients;
private readonly string AuthCouchDbCookieKeyName = "AuthSession";
public CouchDbClients(IOptions<Clients> clients)
{
this.clients = clients ??
throw new ArgumentNullException(nameof(couchDbCredentials));
this.couchDbCredentials = new Dictionary<string, Credentials>();
this.couchDbCredentials.Add(NamedHttpClients.COUCHDB, this.clients.Value.Work);
this.authSessions = new Dictionary<string, string>();
}
public async Task<HttpResponseMessage> AuthenticatedQuery(Func<Task<HttpResponseMessage>> query, string name, HttpClient client)
{
int counter = 0;
var response = new HttpResponseMessage();
do
{
Authenticate(name, client);
response = await query();
if (response.IsSuccessStatusCode)
{
break;
}
else if (response.StatusCode == HttpStatusCode.Unauthorized)
{
this.authSessions[name] = GenerateCookie(client, name);
}
else
{
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
}
counter ;
} while (counter < 3);
return response;
}
private void Authenticate(string name, HttpClient client)
{
CookieContainer container = new CookieContainer();
var session = this.authSessions.ContainsKey(name);
if (!session)
{
var newCookie = GenerateCookie(client, name);
authSessions.Add(name, newCookie);
}
container.Add(
client.BaseAddress,
new Cookie(AuthCouchDbCookieKeyName, this.authSessions[name])
);
}
private string GenerateCookie(HttpClient client, string name)
{
string authPayload = JsonConvert.SerializeObject(this.couchDbCredentials[name]);
var authResult = client.PostAsync(
"_session",
new StringContent(authPayload, Encoding.UTF8, "application/json")
).Resu<
if (authResult.IsSuccessStatusCode)
{
var responseHeaders = authResult.Headers.ToList();
string plainResponseLoad = authResult.Content.ReadAsStringAsync().Resu<
var authCookie = responseHeaders
.Where(r => r.Key == "Set-Cookie")
.Select(r => r.Value.ElementAt(0)).FirstOrDefault();
if (authCookie != null)
{
int cookieValueStart = authCookie.IndexOf("=") 1;
int cookieValueEnd = authCookie.IndexOf(";");
int cookieLength = cookieValueEnd - cookieValueStart;
string authCookieValue = authCookie.Substring(cookieValueStart, cookieLength);
return authCookieValue;
}
else
{
throw new Exception("There is auth cookie header in the response from the CouchDB API");
}
}
else
{
throw new HttpRequestException(string.Concat("Authentication failure: ", authResult.ReasonPhrase));
}
}
}
и вот мое модульное тестирование :
[Fact]
public async Task Should_NotThrowHttpRequestException_When_AMessageHistoryIsCreated()
{
var recipients = MockMessage.GetRecipients(279, 1, 2, 3);
var message = MockMessage.GetMessage(recipients);
mockStateFixture
.MockMessageHistoryService
.Setup(service => service.CreateMessageHistory(message));
// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Created
})
.Verifiable();
// create the mock client
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://10.179.236.207:5984/"),
};
mockStateFixture.MockIHttpClientFactory.Setup(x => x.CreateClient(NamedHttpClients.COUCHDB))
.Returns(httpClient);
//var httpResponseMessage = new Mock<Task<HttpResponseMessage>>();
//httpResponseMessage.Setup
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Created
};
//httpResponseMessage.Setup(x => x.StatusCode = HttpStatusCode.Created);
//mockStateFixture.MockCouchDbClient.Setup(x => x.AuthenticatedQuery(
// async () =>
// {
// return await httpResponseMessage;
// },
// NamedHttpClients.COUCHDB,
// httpClient))
// .Returns(It.IsAny<Task<HttpResponseMessage>>);
var messageHistoryService = new MessageHistoryService(
mockStateFixture.MockIHttpClientFactory.Object, mockStateFixture.MockCouchDbClient.Object);
var task = messageHistoryService.CreateMessageHistory(message);
var type = task.GetType();
Assert.True(type.GetGenericArguments()[0].Name == "VoidTaskResult");
Assert.True(type.BaseType == typeof(Task));
await task;
}
Как мне издеваться над ICouchDbClients, мне вообще нужно?
Это ошибка, которую я получаю:
Имя теста:
Данные.Тесты.MessageHistoryServiceTests CreateMessageHistory.should_notthrowhttprequestexception_when_amessagehistoryсоздал полное имя теста: Data.Тесты.MessageHistoryServiceTests CreateMessageHistory.Should_NotThrowHttpRequestException_When_AMessageHistoryСоЗданный тестовый источник: C:Data .Тесты MessageHistoryServiceTests.cs : строка 36 Результат теста: Сбой Продолжительность теста: 0:00:00.393
Отслеживание стека результатов:
в Data.API.MessageHistoryService.Создайте MessageHistory (сообщение Message) в C:DataAPIMessageHistoryService.cs:line 51 в данных.Тесты.MessageHistoryServiceTests.Создайте историю сообщений.Should_NotThrowHttpRequestException_When_AMessageHistoryIsCreated() в C:Data .Тесты MessageHistoryServiceTests.cs: строка 100 — Конец трассировки стека из предыдущего местоположения, где было сгенерировано исключение — Сообщение о результате: System.NullReferenceException : ссылка на объект не установлена для экземпляра объекта.
Комментарии:
1. Пожалуйста, добавьте ошибку, которую вы получаете, и более подробно объясните, что вы пытаетесь сделать с ожидаемым результатом.
2. Добавили ли вы точку останова в свой код и выполнили ли вы поиск, чтобы узнать, что на самом деле равно null?
3. Смысл макета в том, чтобы удалить переменные, чтобы вы могли сосредоточиться на той части системы, которую вы на самом деле пытаетесь протестировать. Таким образом, ваши макеты должны быть как можно более высокоуровневыми. Например, вам не нужно издеваться над
HttpMessageHandler
. Просто создайте макетHttpClient
и заглушите его,SendAsync
чтобы вернуть готовый файлHttpResponseMessage
. В случае вашегоICouchDbClients
, создайте макет этого и заглушкуAuthenticatedQuery
полностью, чтобы снова вернуть консервированныйHttpResponseMessage
(который, похоже, является возвращаемым типом этого). Не имеет значения, что на самом деле делает этот метод.4. Просто создайте
new Mock<ICouchDbClient>()
, затем настройтеAuthenticatedQuery
, чтобы вернутьHttpResponseMessage
5. используйте либо
Task.FromResult
, либо MoqReturnsAsync