Я использую NEST для выполнения запросов elasticsearch.
public IReadOnlyCollection<IHit<Recommendation>> GetRecommendations(
RecommenderQueryFields shoulds,
RecommenderQueryFields musts,
RecommenderQueryFields mustNots)
{
var boolQuery = new BoolQuery();
boolQuery.Should = GetQueryContainers(shoulds);
boolQuery.Must = GetQueryContainers(musts);
boolQuery.MustNot = GetQueryContainers(mustNots);
var response = _elasticClient.Search<Recommendation>(s => s
.AllTypes().From(0).Size(10)
.Query(outerQuery => boolQuery));
return response.Hits;
}
У меня есть логика в методе GetQueryContainers, которую я хочу проверить.
Есть ли способ проверить, что было передано ElasticClient в объекте boolQuery?
Я уже пробовал следующие вещи, используя NUnit и NSubstitute.
[Test]
public void Test1()
{
// Arrange
var searchResponse = Substitute.For<ISearchResponse<Recommendation>>();
searchResponse.Hits.Returns(new List<IHit<Recommendation>>());
var elasticClient = Substitute.For<IElasticClient>();
var sut = new Recommender(elasticClient);
// Act
sut.GetRecommendations(null, null, null);
// Assert
elasticClient
.Received(1)
.Search(Arg.Is<Func<SearchDescriptor<Recommendation>, ISearchRequest>>(x => true));
}
В Arg.Is<[...]>(x => true) я хотел бы заменить константу true для некоторых проверок на boolQuery. Но я не знаю, возможно ли это и как это делается. Или есть другой способ сделать это?





TL;DR Использовать производный QueryVisitor. См. Edit2 ниже.
Обнаружил, что на вопрос уже был дан ответ. Это связано не с NEST, а с тестированием лямбда-выражений. Это невозможно: Модульное тестирование C# Moq с делегатом Lambda Expression или Func
Что можно сделать, так это протестировать запрос JSON, который будет отправлен в elasticsearch, но тогда вам нужен фактический ElasticClient: ElasticSearch NEST 5.6.1 Запрос для модульного теста
Что можно сделать, так это поместить логику в собственный метод/класс. Но тогда вы пишете код просто ради тестирования, которое я не люблю. Нравится:
public BoolQuery GetBoolQuery(RecommenderQueryFields shoulds, RecommenderQueryFields musts,
RecommenderQueryFields mustNots)
{
var boolQuery = new BoolQuery();
boolQuery.Should = GetQueryContainers(shoulds);
boolQuery.Must = GetQueryContainers(musts);
boolQuery.MustNot = GetQueryContainers(mustNots);
return boolQuery;
}
Вы выставляете метод public, который вы не собираетесь использовать, только для тестирования.
Но затем вы можете утверждать о boolQuery следующим образом:
[Test]
public void GetRecommendations_CallsElasticSearch()
{
// Arrange
var elasticClient = Substitute.For<IElasticClient>();
var sut = new Recommender(elasticClient);
// Act
var boolQuery = sut.GetBoolQuery(new RecommenderQueryFields{BlackListedFor = new List<string>{"asdf"}}, null, null);
// Assert
Assert.AreEqual(1, boolQuery.Should.Count());
}
В boolQuery.Should есть список QueryContainer, которые нельзя тестировать, потому что он также генерируется с помощью лямбда-выражений. Хотя это лучше, чем ничего, это все же не самый лучший способ протестировать NEST.
@Russ Cam в комментарии упомянул IQueryContainer и QueryVisitor
Что у меня есть:
[Test]
public void test()
{
// Arrange
var fieldValue = "asdf";
var elasticClient = Substitute.For<IElasticClient>();
var sut = new Recommender(elasticClient);
// Act
var boolQuery = sut.GetBoolQuery(new RecommenderQueryFields { BlackListedFor = new List<string> { fieldValue } }, null, null);
// Assert
IQueryContainer qc = boolQuery.Should.First(); // Cast to IQueryContainer
Assert.AreEqual(fieldValue, qc.Match.Query); // Assert value
// Get "field name"
var queryVisitor = new QueryVisitor();
var prettyVisitor = new DslPrettyPrintVisitor(new ConnectionSettings(new InMemoryConnection()));
qc.Accept(queryVisitor);
qc.Accept(prettyVisitor);
Assert.AreEqual(0, queryVisitor.Depth);
Assert.AreEqual(VisitorScope.Query, queryVisitor.Scope);
Assert.AreEqual("query: match (field: blacklistedfor.keyword)\r\n", prettyVisitor.PrettyPrint);
}
Доступ к значению поля можно получить через IQueryContainer.
Я пробовал QueryVisitor и DslPrettyPrintVisitor. Первый не дает никакой полезной информации. Он имеет 0 глубины и это запрос? Я уже знаю, что. Со вторым я могу указать некоторую дополнительную информацию, такую как имя поля (черный список) и суффикс (ключевое слово).
Не идеально, чтобы утверждать о строковом представлении, но лучше, чем ничего.
@Russ Cam дал мне решение, которым я очень доволен. Он использует производный QueryVisitor:
public class MatchQueryVisitor : QueryVisitor
{
public string Field { get; private set; }
public string Value { get; private set; }
public override void Visit(IMatchQuery query)
{
var inferrer = new Inferrer(new ConnectionSettings(new InMemoryConnection()));
Field = inferrer.Field(query.Field);
Value = query.Query;
}
}
[Test]
public void test()
{
// Arrange
var fieldValue = "asdf";
var elasticClient = Substitute.For<IElasticClient>();
var sut = new Recommender(elasticClient);
// Act
var boolQuery = sut.GetBoolQuery(new RecommenderQueryFields { BlackListedFor = new List<string> { fieldValue } }, null,
null);
// Assert
IQueryContainer qc = boolQuery.Should.First();
var queryVisitor = new MatchQueryVisitor();
qc.Accept(queryVisitor);
Assert.AreEqual(fieldValue, queryVisitor.Value);
Assert.AreEqual("blacklistedfor.keyword", queryVisitor.Field);
}
Таким образом, в MatchQueryVisitor он получает Field и Value, которые затем утверждаются в тестовом методе.
Очень хорошо. Спасибо, что указали мне на QueryVisitor и IQuerycontainer. Обновил мой ответ.
Вы можете получить посетителя из QueryVisitor и переопределить любые методы, которые вам нужны для вашего варианта использования.
Ты мой герой. Я обновил свой ответ. Теперь я доволен кодом.
@SimonLang, это здорово. Спасибо за подробный ответ. Очень полезно!!!
Существует
QueryVisitor, который может посетитьQueryContainerи проверить, содержит ли он запрос указанного типа. Другой подход, если вы знаете, какие запросы ожидать, заключается в приведении кIQueryContainerи чтении соответствующего свойства запроса.