Xunit & MOq - Unable to setup expectation for IndexDocumentsAsync - c#

I have a method that inserts documents into Azure cognitive search but I am struggling to unit-test the method. My method looks like below:
public async Task AddDocumentIntoIndexAsync(Document[] Documents, string IndexName)
{
IndexDocumentsBatch<CreClaim> batch = IndexDocumentsBatch.MergeOrUpload(Documents);
try
{
IndexDocumentsResult result = await _searchClient.IndexDocumentsAsync(batch);
}
catch (Exception ex)
{
}
}
}
I am using dependency injection to inject _searchClient which is an instance of type Azure.Search.Documents.SearchClient.
In my unit test project, I am mocking my SearchClient as below:
_searchClient = new Mock<SearchClient>(new Uri(endpoint),indexName, new AzureKeyCredential(key));
The problem happens when I try to setup IndexDocumentsAsync.
var indexresult = SearchModelFactory.IndexDocumentsResult;
_searchClient.Setup(x => x.IndexDocumentsAsync(batch, options, new CancellationToken())).Returns(indexresult);
When I run this code, I get a runt time error:
: 'Invalid callback. Setup on method with 3 parameter(s) cannot invoke callback with different number of parameters (1).'
I thought may be its because the last 2 params of the method IndexDocumentsAsync are optional so I tried with
_searchClient.Setup(x => x.IndexDocumentsAsync(batch)).Returns(indexresult);
With this I get a compilation issue:
Can anyone help please?

Related

How to get relevant error message when method called with unexpected parameter?

In Moq, I want to assert that a method TestMethod is called with a specific parameter. When the test fails, I want to see a useful error message similar to:
TestMethod called with unexpected value X for parameter P where Y expected.
public interface ITestObject
{
void TestMethod(int parameter);
}
Just for sake of illustration, I could achieve this using a handcoded mock as follows:
Assert.AreEqual failed. Expected:<3>. Actual:<2>. TestMethod called with unexpected value for parameter 'actual'.
public class MockTestMethodParameter : ITestObject
{
private readonly int expected;
public MockTestMethodParameter(int expected) { this.expected = expected; }
public void TestMethod(int actual)
{
Assert.AreEqual(actual, expected, $"{nameof(TestMethod)} called with unexpected value for parameter '{nameof(actual)}'.");
}
}
[TestMethod]
public void TestHandcodedMockFailure()
{
var mock = new MockTestMethodParameter(expected: 2);
mock.TestMethod(actual: 3);
}
My problem is, I can't figure out how to do this in Moq. When I set up my Mock and call Verify(), I get the following unexpected and unclear error message, instead of the message I was hoping for:
Expected invocation on the mock at least once, but was never performed: test => test.TestMethod(It.Is(2, GenericEqualityComparer))
Performed invocations:
MockUnitTest1.ITestObject:1 (test):
UnitTest1.ITestObject.TestMethod(3)
[TestMethod]
public void TestMoqFailure()
{
var expected = 2;
var actual = 3;
var mock = new Moq.Mock<ITestObject>(Moq.MockBehavior.Strict);
mock.Setup(test => test.TestMethod(Moq.It.IsAny<int>())).Verifiable();
mock.Object.TestMethod(actual);
mock.Verify(test => test.TestMethod(Moq.It.Is(expected, EqualityComparer<int>.Default)));
}
Granted, the information is there, but I expected something more along the lines of, "TestMethod was invoked, but with incorrect parameters." It confuses me when Moq reports that TestMethod was not invoked because, intuitively, I did invoke the mock. I called TestMethod with It.IsAny(), as declared in the mock setup.
I've tried many different adjustments, but none yielding the desired result:
Custom error message
Setup with Is(3)
Setup with Is(2)
MockBehavior.Loose
Different .NET platforms, Framework 4.6.1, Core 2.1, .NET 6.0
Is this simply the way Moq reports error results for Verify()? I'm new to Moq, and while this behavior is unexpected to me, perhaps it is working as designed.
This appears to be normal behavior for mock frameworks in general. Doing the same thing with Telerik JustMock yields a similar error message:
Occurrence expectation failed. Expected exactly 1 call. Calls so far: 0
[TestMethod]
public void TestJustMockFailure()
{
var expected = 2;
var actual = 3;
var mock = Mock.Create<ITestObject>(Behavior.Strict);
Mock.Arrange(() => mock.TestMethod(Arg.IsAny<int>()));
mock.TestMethod(actual);
Mock.Assert(() => mock.TestMethod(Arg.Is(expected)), Occurs.Once());
}
In summary:
The OP's use case (my use case) is valid, verifying a particular parameter value was passed to a mocked method. However, the error message does not report the parameter mismatch as such. Rather, it reports that the entire mocked method call did not occur. This is by design, and appears to be common among popular mock frameworks.

How does one set a variable in a command using Mediatr with Moq?

I have a simple functional style test for output of a command that I've written using Mediatr's IRequest and IRequestHandler<>
[Fact]
public void TestReturnValuesAsync()
{
// Arrange
var handler = new Mock<IRequestHandler<SyncSubmerchantDataCommand, CommandResult<int>>>();
handler.Setup(x => x.Handle(It.IsAny<SyncSubmerchantDataCommand>(), It.IsAny<CancellationToken>())).ReturnsAsync(new CommandResult<int>(0, ResultStatus.Success, "string"));
// Act
var result = handler.Object.Handle(new SyncSubmerchantDataCommand(), new CancellationToken());
// Assert
result.Result.Data.ShouldBe(0);
result.Result.Status.ShouldBe(ResultStatus.Success);
result.Result.Message.ShouldBe("string");
}
Since this command runs as a background task, I don't want it interrupted. I have a variable, submerchantList, that is of type List<T> which is used in a foreach loop to do work. The work is set in a try-catch because I don't want the command interrupted, as I stated before. I want to test the output of the what is written to my logs (_log.info) if an exception is thrown during this process.
public class CommandNameHandler : IRequestHandler<source, destination> {
// constructors and privates
public async destination Handle(param, token)
{
var submerchantList = db call.ToList();
foreach (var item in submerchantList)
{
try {
//does work
}
catch (Exception e) {
if (item != null)
_log.info($"{e} - {item.Id}");
}
return some out put
}
The problem is that I can't seem to figure out how to set the value of the any variable, such as the submerchantList within the Handle in order to throw the exception for my next test. I'm stumped.
Any help would be greatly appreciated.
SOLUTION:
Here was the solution: Stubbing the database call by injecting an in-memory DbSet. I used this resource learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/… This issue was db call.ToList It looked something like this _db.Table.Include(x => x.Foreign).Where(x => x.Foreign.Field == Enum.Value).ToListAsync() While I was setting up the Mock DbSet, I had to use the string version, not the LINQ-chain version in the unit test. So, that means mockDbset.Setup(x => x.Table.Include("Foreign")).Returns(myCustomDbSet); Hope that helps someone!

Unit Testing Marten

I'm putting together an implementation of IdentityServer4, using PostgreSQL as the database, Marten as the ORM, and GraphQL as the API. So far, it's working great at runtime. However, I'm also trying to get unit tests in place, and am running into an issue. I have a custom implementation of IdentityServer4's IClientStore interface, where the implementation of the FindClientByIdAsync method looks thus:
public async Task<Client> FindClientByIdAsync(string clientId)
{
var client = await _documentSession.Query<dbe.Client>().FirstOrDefaultAsync(c => c.ClientId == clientId);
return _mapper.Map<Client>(client); // AutoMapper conversion call
}
This works great at runtime. However, I have the following test that I'm trying to put in place to exorcise this code:
[Fact]
public async Task FindClientByIdReturnsClient()
{
var clients = new []
{
new dbe.Client
{
ClientId = "123"
}
}.AsQueryable();
var queryable = new MartenQueryable<dbe.Client>(clients.Provider);
// _documentSession is a Moq Mock
_documentSession.Setup(x => x.Query<dbe.Client>()).Returns(queryable);
var store = new ClientStore(_documentSession.Object, _mapper);
var result = await store.FindClientByIdAsync("123");
Assert.NotNull(result);
Assert.Equal("123", result.ClientId);
}
The error I get happens when the test tries to execute the FindClientByIdAsync method:
System.InvalidCastException : Unable to cast object of type 'System.Linq.EnumerableQuery`1[StaticSphere.Persona.Data.Entities.Client]' to type 'Marten.Linq.IMartenQueryable'.
If anyone is familiar with Marten can could provide some insight, that would be great! I've done my Google time, and haven't found anything concrete on the subject.
A quote from the creator of Marten which could be relevant here (context):
You can mock a bit of IDocumentSession (Load, Store, SaveChanges,
maybe query by compiled query), but you’re gonna be in a world of hurt
if you try to mock the Linq support.
So one solution would be to do integration tests for which you can find some code from the official Marten's repository or here.

How to unit test Hangfire implementation using Hangfire.MemoryStorage

To clarify what I'm trying to do, I have a method that creates/updates an item in my DB. When Testing as usual by asserting that the item is in the in-memory database always comes back as null. After reading the docs, it appears I need to be determining whether or not the correct job has been added to the Hangfire queue.
I've been going over the documentation for unit testing a Hangfire implementation but can't seem to find a way to test against in-memory storage.
In the example, a mock is created and then passed into the constructor of the controller. However, I've been modeling my solution after this example where jobs are queued in the Configure method of the Startup class. This means I have no controller and I'm not passing anything into my methods that are queuing up my jobs.
So how should I be going about testing this?
Here's one of the methods I'm trying to test:
public async Task SyncDeletedAccounts()
{
try
{
int pastMinutes = 90;
var startDate = DateTime.Now.AddMinutes(0 - pastMinutes);
var client = await this._forceDotComService.GetForceClient();
var deletedIds = await this._forceDotComService.GetDeletedIds<sf.Account>(client, startDate);
if (!deletedIds.Any())
{
this._logger.LogInformation("No deleted accounts");
return;
}
foreach (var deletedId in deletedIds)
{
BackgroundJob.Enqueue<InterconnectorServiceHelper<sf.Account, ic.Account>>(i =>
i.MarkDeleted(deletedId));
}
}
catch (Exception e)
{
this._logger.LogError(e, "AccountService.SyncDeletedAccounts");
throw;
}
}

To Verify a mock, is a return required to verify

I am having trouble getting my Verify to work on a DB call.
I have a method that I am simply trying to verify that a database call was made.
I can't post the real code but here is a close example.
protected void ReportDB(uint waitTimeInMinutes)
{
//check database connection
Status dbStatus = Status.Ok;
string dbComment = "ok";
try
{
Data.GetActive("1");
}
catch (Exception ex)
{
dbComment = "Unable to access the database: " + ex.Message;
dbStatus = Status.Critical;
}
//Report Status.
}
So basically the GetActive() method just makes a database call. If it doesn't throw an exception then we are good and connectivity is up.
My test method is.
[TestMethod]
public void ReportDBStatusTest()
{
_fakeData.Setup(s => s.Data.GetActive(It.IsAny<string>()));
_unitUnderTest = new Service();
_unitUnderTest.ReportDB(0);
_fakeData.Verify(s => s.Data.GetActive(It.IsAny<string>()), Times.Once());
}
I debug through and the method is called and everything, yet the verify says it was called Times.Never. I think I may just be misunderstanding how to do this correctly.
Error:
Expected invocation on the mock once, but was 0 times: s => s.Data.GetActive(It.IsAny())
Configured setups and invocations:
The error is expected. This is because the 'Data' object inside the 'ReportDB' object is not the same as the 'Data' object inside the '_fakeData' object.
One workaround would be to externalize the 'Data' object in your 'ReportDB' object so that it can be mocked. Else, you need to alter your unit test.

Categories

Resources