Test for parameter passed to NEST (elasticsearch) - c#

I am using NEST to do elasticsearch queries.
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;
}
I have logic in the GetQueryContainers method which I want to test.
Is there some way to check what has been passed to the ElasticClient in the boolQuery object?
I have tried the following stuff already, using NUnit and 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));
}
In the Arg.Is<[...]>(x => true) I would like to replace the true constant for some checks on boolQuery. But I do not know if it is possible or how it is done. Or is there a different way to do this?

TL;DR Use a derived QueryVisitor. See Edit2 below.
Found that the question has been answered already. It is not related to NEST, but to testing lambda expressions.
It is not possible: C# Moq Unit Testing with Lambda Expression or Func delegate
What can be done is testing the JSON request which will be sent to elasticsearch, but then you need the actual ElasticClient: ElasticSearch NEST 5.6.1 Query for unit test
What can be done is putting the logic in its own method/class. But then you write code simply for the sake of testing, which I'm not a fan of. Like:
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;
}
You are exposing a public method which you are not intending for use, only for testing.
But you can then assert on boolQuery like this:
[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());
}
In boolQuery.Should is a list of QueryContainer which are not testable because it is generated with lambdas aswell. While better than nothing, it is still not a clean way to test NEST.
Edit
#Russ Cam in the comment has mentioned the IQueryContainer and QueryVisitor
What I've got:
[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);
}
The value of the field can be accessed via IQueryContainer.
I tried the QueryVisitor and the DslPrettyPrintVisitor. The first one doesn't provide any useful information. It has 0 depth and it is a Query? I already know that. With the second one I can assert some additional information, like the field name (blacklistedfor) and the suffix (keyword).
Not perfect to assert on the string representation, but better than nothing.
Edit2
#Russ Cam gave me a solution which I am really happy with. It uses a derived 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);
}
So in MatchQueryVisitor, it gets the Field and Value, which are then asserted in the test method.

Related

Elasticsearch Nest unit tests with Moq : fail to mock concrete class

I need to mock some methods and properties in Nest.IElasticClient interface that is used in my repo.
As example I need to stub the Indices.Exists() method to return an ExistsResponse having an Exists property returning true.
The problem is that the concrete class has no interface implementation, nor setter on the Exists property, and is not declared virtual neither in Nest lib:
public class ExistsResponse : ResponseBase
{
public ExistsResponse();
public bool Exists { get; }
}
public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);
So for the mocking I tried to set the property anyway on the concrete class, but it failed with all the following methods, I have no idea on how to do ...
/* Fail with exception :
System.NotSupportedException : Unsupported expression: x => x.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock1 = new Mock<ExistsResponse>();
obj.SetupGet(f => f.Exists).Returns(true);
/* Fail with exception :
System.NotSupportedException : Unsupported expression: f => f.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock2 = Mock.Of<ExistsResponse>(x => x.Exists == true);
/* Fail with exception :
System.ArgumentException : Property set method not found.
*/
var mock3 = new ExistsResponse();
var property = typeof(ExistsResponse).GetProperty("Exists", BindingFlags.Public | BindingFlags.Instance);
property.SetValue(mock3, true);
/* Fail with exception :
System.NullReferenceException (setter is null)
*/
var mock4 = new ExistsResponse();
var setter = property.GetSetMethod(true);
setter.Invoke(mock4, new object[] { true });
// My Mock on the Indices.Exists method
var elasticMock = new Mock<IElasticClient>();
elasticMock
.Setup(x => x.Indices.Exists(It.IsAny<string>(), null))
.Returns(/*** my stubbed object here ***/); // <== how to stub the concrete class to return a ExistsResponse.Exists = true ?
My libs :
Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5
Thank you for your help
It is usually not good to mock what you don't own but if you want to mock elastic client responses the best approach will be to use InMemorryConnection, you can instantiate a new elasticClient with InMemorryConnection like this:
InMemorry Elastic Client:
var connection = new InMemoryConnection();
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(settings);
Then you will need to fake the response and ask client to return it like below:
var response = new
{
took = 1,
timed_out = false,
_shards = new
{
total = 2,
successful = 2,
failed = 0
},
hits = new
{
total = new { value = 25 },
max_score = 1.0,
hits = Enumerable.Range(1, 25).Select(i => (object)new
{
_index = "project",
_type = "project",
_id = $"Project {i}",
_score = 1.0,
_source = new { name = $"Project {i}" }
}).ToArray()
}
};
var responseBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response));
var connection = new InMemoryConnection(responseBytes, 200);
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection).DefaultIndex("project");
var client = new ElasticClient(settings);
var searchResponse = client.Search<Project>(s => s.MatchAll());
You can find more information about InMemorryConnection here.
If you want to properly follow don't mock what you don't own you need to do following steps:
Wrap ElasticClient and all Request and Responses of ElasticClient in this library, then you can easily mock what you need.
You should test your wrapper class with Integration test to be sure your wrapped class work as expected.
UPDATE:
For mocking Indices.Exists you need only to send 200 OK so you only need to do this:
var connection = new InMemoryConnection(responseBytes, 200);

How to write a unit test for a method which calls other services

I have a following method which is calling another service , but there is a some data manipulation before calling internal service. Please see below
public async Task<myObject> Save(string server,string username, myObject element)
{
string temp = element.Class + "_" + element.Description + "_" + username;
var saveData = new string[] { element.Name, temp};
var response = await datService.save(server, saveData);
var result = response.TomyObject(); // this is the extension method to convert response to myObject.
return result ;
}
What will be the best way to unit test?
I tried following but just want to make sure I am doing the "correct" way.
public async Task Save_Success()
{
//ARRANGE
var element = new myObject
{
Name = "Test",
Description = "Test Save",
Class = "test class"
};
string temp = element.Class+ "_" + element.Description + "_" + username;
var saveData = new string[] { element.Name, temp};
var response = new anotherObject
{
Name= "Test",
MyArray = new objArray
{
P0 = saveData[0],
P1 = saveData[1]
},
Error = ""
};
var datService = new Mock<IDataService>();
datService.Setup(x => x.Save(It.IsAny<string>(), It.IsAny<string[]>()).Returns(Task.FromResult(response));
var myClass = new MyClass(datService.Object);
//ACT
var result = await myClass.Save("testServer","testUser", element);
// ASSERT
Assert.AreEqual(response.MyArray.P0, result.Class);
}
Here is the extension method
public static myObject TomyObject(this anotherObject response)
{
var entity = new myObject();
entity.Name = response.Name;
if(response.MyArray!= null && response.MyArray.P1 != "")
{
string[] details = response.MyArray.P1.Split('_');
entity.Class = details[0];
entity.Description = details[1];
}
return entity;
}
Here is how you could test your extension method:
anotherObject response = new anotherObject
{
Name = "TestName",
MyArray = new objArray
{
P1 = "Val1_Val2"
}
};
var result = response.TomyObject();
Assert.Equal("TestName", result.Name);
Assert.Equal("Val1", result.Class);
Assert.Equal("Val2", result.Description);
The key is to have your unit tests only test one unit of work. If you want to test your extension method, write a test for it in isolation. Once you start including multiple units of work (i.e. the IDataService), you are actually testing a system and it becomes an integration test.
UPDATE:
On this line in your Save_Success test:
// ASSERT
Assert.AreEqual(response.MyArray.P0, result.Class);
You are actually testing something that is not the responsibility of the Save method (setting the Class property is actually the responsibility of your TomyObject, which could be tested separately).
To verify the Save method is doing what it's supposed to, you could do this:
// ASSERT
datService.Verify(x => x.Save(It.IsAny<string>(), It.IsAny<string[]>()), Times.Once);
That way, you're only testing that the Save method is doing what it's explicitly responsible for doing.

Checking the expected valued passed to a delegate using Rhino.Mocks

I am attempting to write a test case that will confirm that the value of a parameter passed into the method under test was used to invoke a delegate function that is passed as an argument to a method on a mocked service. The example below illustrates what I am doing:
The method under test:
public IList<IResultData> GetResultData(int id)
{
Func<IExternalInterface, List<ExternalData> getDataMethod = client => client.GetExternalData(id);
List<ExternalData> externalDataList = serviceClient.ExecuteService(getDataMethod, "GetExternalData");
var result = new List<ResultData>();
//Some code to convert externalDataList into an IList<IResultData>
return result;
}
The test method:
[TestMethod]
public void TestMethodCall()
{
var mockServiceClient = MockRepository.GenerateMock<IServiceClient>();
mockServiceClient.Expect(x => x.ExecuteService(
Arg<Func<IExternalInterface, List<ExternalData>>.Is.NotNull,
Arg<string>.Is.Equal("GetExternalData")));
var myClass = new MyClass(mockServiceClient);
var result = myClass.GetResultData(3);
//Some assertions that result is as expected.
}
Is there a way to assert that the function passed into the Execute method is being passed the id of 3 from the test method's call to GetResultData()? The service being mocked is in a library not under my control so I can't change it's signature.
The following pattern will allow you to assert the lambda:
[TestMethod]
public void TestMethod1()
{
var wasCalled = false;
var fakeService = MockRepository.GenerateStub<IExternalInterface>();
fakeService.Stub(x => x.GetExternalData(1))
.Return(new List<ExternalData>() {new ExternalData() {Id = 1}});
fakeService.Stub(service => service.ExecuteService(Arg<Func<IExternalInterface,
List<ExternalData>>>.Is.Anything, Arg<string>.Is.Anything))
.WhenCalled(invocation =>
{
wasCalled = true;
var func = (Func<IExternalInterface, List<ExternalData>>) invocation.Arguments[0];
var res = func(fakeService);
//
// Assert here the "res"
//
}).Return(null);
var target = new MyClass(fakeService);
target.GetResultData(1);
Assert. IsTrue(wasCalled);
}
However based you the code you've provided a better solution would be to use Do method as the following:
[TestMethod]
public void TestMethod1()
{
var fakeService = MockRepository.GenerateStub<IExternalInterface>();
fakeService.Stub(x => x.GetExternalData(1)).Return(new List<ExternalData>() {new ExternalData() {Id = 1}});
fakeService.Stub(service => service.ExecuteService(Arg<Func<IExternalInterface,
List<ExternalData>>>.Is.Anything, Arg<string>.Is.Anything))
.Do(new Func<Func<IExternalInterface, List<ExternalData>>, string, List<ExternalData>>((func, str)=>func(fakeService)));
var target = new MyClass(fakeService);
var result = target.GetResultData(1);
//
// Assert here the "result"
//
}

How do I get this mock to work?

I need to make changes to a legacy class with no tests so I have started by writing a test, however the mocking (using Moq) isn't working properly.
This my test
[Test]
public void CorrectlyCopiesToLightningWhenNoLocationsExist()
{
// arrange
long orgId = Id64.NewId();
var data = LightningMapLocationsHelperTestData.ForNormalCopy(orgId);
var organisation = new Group
{
GroupId = orgId,
Name = "Test Organisation",
Type = GroupType.OrganisationGroup
};
var groupRepo = new Mock<IGroupRepository>();
groupRepo.Setup(r => r.GetGroup(orgId)).Returns(organisation);
var orgRepo = Mock.Of<IOrganisationRepository>(o => o.LightningLocationsEnabledFor(orgId));
var mapLocationRepo = new Mock<IMapLocationRepository>();
mapLocationRepo.Setup(r => r.OrganisationRepository).Returns(orgRepo);
mapLocationRepo
.Setup(r => r.GetMapLocationsByGroupIds(orgId, It.IsAny<IEnumerable<long>>(), true, true))
.Returns(data.InitialDatabaseLocations);
var lightningMapLocationRepo = new Mock<ILightningMapLocationRepository>();
lightningMapLocationRepo
.Setup(r => r.LocationsById(orgId, data.InitialLightningLocations.Select(l => l.LocationId)))
.Returns(data.InitialLightningLocations);
lightningMapLocationRepo
.Setup(r => r.AddLocations(It.IsAny<List<Location>>()))
.Callback((List<Location> locations) => data.InitialLightningLocations.AddRange(locations));
var infoMessages = new List<string>();
var errorMessages = new List<string>();
var helper = new LightningMapLocationsHelper(
(string s, object[] args) => infoMessages.Add(string.Format(s, args)),
(string s, object[] args) => errorMessages.Add(string.Format(s, args)));
List<CopyFailure> copyFailures;
// act
bool success = helper.CopyLocationsForOrganisation(orgId, 10, out copyFailures);
// assert
success.ShouldBeTrue();
(errorMessages?.Count ?? 0).ShouldBe(0);
data.InitialLightningLocations.Count.ShouldBe(data.ExpectedLightningLocations.Count);
}
Inside LightningMapLocationsHelper is the following method
private Group GetValidOrganisationGroup(long groupId)
{
var organisation = (new GroupRepository()).GetGroup(groupId);
if (organisation != null && organisation.Type == GroupType.OrganisationGroup) return organisation;
LogErrorMessage("Invalid groupId: {0}. Ignoring...", groupId);
return null;
}
that when called is using an actual instance of GroupRepository rather than the groupRepo mock set up in the test, thus causing the test to fail. As GroupRepository implements IGroupRepository I expected this to work.
public class GroupRepository : IGroupRepository {…}
Perhaps I am misunderstanding how mocking works. Can someone offer some insight to help me understand why this doesn't work, and how I can fix it? Do I have to pass the mocked classes in?

Helper method for testing JsonResult in Nunit

Here is my controller that returns a JSON.
public JsonResult GetValues()
{
return Json(
new{
title = new {
text = "Hello World"
},
xAxis = new {
type = "List of countries"
labels = new {
rotation = 90
}
}
},JsonRequestBehavior.AllowGet);
}
And in my Nunit, i am testing as follows:
[Test]
public void TestGetValues()
{
var controller = new HelloWorldController();
var values = controller.GetValues() as JsonResult;
Assert.IsNotNull(data);
var title = values.Data.GetType().GetProperty("title")
.GetValue(values.Data,null);
var text = title.GetType().GetProperty("text").GetValue(title);
Assert.IsNotNull(text);
}
This works fine but i have to test several methods that has same properties which will require me to write same thing over and over again. How to write a helper method so that i can just pass in the controller and the property i want to test.
Something like this:
var checkText = GetJSonProperties(controllername, "data/title/text");
var checkXais = GetJSonProperties(controllernmae, "data/xAxis/Type");
How can i achieve this ?
I would just use a dynamic type.
var controller = new HelloWorldController();
dynamic values = controller.GetValues();
var title = (string)values.title;
Saves you writing helper methods, which themselves may need to be tested!

Categories