Reputation: 3029
I fairly new to the Moq Testing framework and am currently trying to create a mocked method that takes an object as one of its arguments. However, I'd like to verify that the object that it is being passed has the correct properties set for me to continue.
I have something similar to:
private ICustomClient GetMockClient()
{
var client = new Mock<ICustomClient>();
var query = new CustomQueryModel
{
Alias = "alias:12345",
Type = QueryTypes.Alias
}
client
.Setup(client => client.GetAccountByAlias(
new[] {It.Is<CustomQueryModel>(p => p.Alias == query.Alias && p.Type == query.Type
)}, CancellationToken.None))
.ReturnsAsync(new ResultModel()
{
results = new List<ResultModel>()
{ ... }
});
return client.Object
}
When I do the comparison in the following way, I get:
Unsupported expression: new [] {Is(p => ((p.Alias == value(
For some more context, the GetAccountByAlias
method takes the following parameters: GetAccountByAlias(IEnumerable<CustomQueryModel> query, CancellationToken cancellationToken)
How can I, using Moq, or other C# techniques, verify that the correct object, in this case, query
is the one being passed into the method.
Thanks
Upvotes: 1
Views: 1021
Reputation: 1333
There are a few issues with your code, but it is reasonably easily fixed.
Firstly, some plumbing because the rest of us don't have your class definitions
public enum QueryTypes
{
Alias
}
public class CustomQueryModel
{
public string Alias
{
get;
set;
}
public QueryTypes Type
{
get;
set;
}
}
public class ResultModel
{
}
public interface ICustomClient
{
IEnumerable<ResultModel> GetAccountByAlias(IEnumerable<CustomQueryModel> query, CancellationToken cancellationToken);
}
public class ClassUnderTest
{
private readonly ICustomClient client;
public ClassUnderTest(ICustomClient client)
{
this.client = client;
}
public IEnumerable<ResultModel> MethodConsumingGetAccountByAlias(IEnumerable<CustomQueryModel> queries, CancellationToken cancellationToken)
{
// Do whatever your class does before / after it calls client.GetAccountByAlias
return this.client.GetAccountByAlias(queries, cancellationToken);
}
}
I have renamed your interface query parameter to queries because you permit multiple, and it gets very confusing when dealing with a query object being a single instance and a list of those objects with the same name.
The test case code would be as follows, but first a few quick notes:
My version of Moq doesn't support ReturnsAsync. I'll leave the conversion as exercise to the reader as it is pretty straight forward.
You will notice that I am relaxed on the Setup method. Test cases should do two things well:
Too many people forget (2) when writing test cases. Sure, you can do all this in your setup and it will fulfill (1) perfectly, but in the event of a failure, you will have to drill in. By way of example, imagine MethodConsumingGetAccountByAlias above didn't simply return the response from client, but performed a .Where() expression on it. If the wrong parameter was passed to client, your mock client would return null. null.Where() is going to give you a NullReferenceException. Will you figure out what is wrong? For sure, but it will take some effort. This is why the mock below is setup to always return a proper output. The Verify method is where you confirm that the client got called with the desired parameters.
[Test]
public void ShouldCallClientWithCorrectQueryObjects()
{
// Arrange
Mock<ICustomClient> client = new Mock<ICustomClient>();
// Permissive on the setup, return good data no matter what the input.
client.Setup(s => s.GetAccountByAlias(It.IsAny<IEnumerable<CustomQueryModel>>(), It.IsAny<CancellationToken>()))
.Returns(new List<ResultModel>());
CustomQueryModel query = new CustomQueryModel
{
Alias = "alias:12345",
Type = QueryTypes.Alias
};
CancellationToken cancellationToken = new CancellationToken();
// Act
ClassUnderTest test = new ClassUnderTest(client.Object);
IEnumerable<ResultModel> actualResults = test.MethodConsumingGetAccountByAlias(new[] { query }, cancellationToken);
// Assert
Func<IEnumerable<CustomQueryModel>, bool> validateModelFunc = models =>
{
// Test what you want to about your query parameter
List<CustomQueryModel> results = models.ToList();
Assert.That(results.Count, Is.EqualTo(1), "Too many elements in queries");
Assert.That(results[0], Is.SameAs(query), "Unexpected CustomQueryModel object");
return true;
};
// Now we can check that test called client.GetAccountByAlias with the right parameters
client.Verify(v => v.GetAccountByAlias(It.Is<IEnumerable<CustomQueryModel>>(m => validateModelFunc(m)),
cancellationToken),
Times.Once());
Assert.That(actualResults, Is.Not.Null); // or whatever
}
Upvotes: 1