Mocking async query operations - c#

I created unit test and I have to mock context of my EF. I use Moq library and Xunit. I have a test method like this:
[Fact]
public async Task DeleteAttachmentCommandHandler_WithValidCommand_ShouldCallSaveChangesAsyncOnce()
{
var command = new DeleteAttachmentCommand { Id = Guid.NewGuid() };
var attachments = new List<Attachment>();
var dbSetMock = attachments.AsQueryable().BuildMockDbSetForAsyncQueryOperations();
_context.Setup(x => x.Set<Attachment>()).Returns(dbSetMock.Object);
_context.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1).Verifiable();
await Act(command);
_context.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
The _context is of type Mock<IEmployeeSettlementsDbContext>
The BuildMockDbSetForAsyncQueryOperations is my extension method thanks to the MSDN documentation -> https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking, which uses the async providers like TestDbAsyncEnumerable, TestDbAsyncEnumerator, TestDbAsyncQueryProvider. And my extension BuildMockDbSetForAsyncQueryOperations looks like this:
public static Mock<IDbSet<TEntity>> BuildMockDbSetForAsyncQueryOperations<TEntity>(this IQueryable<TEntity> data) where TEntity : class
{
var dbSetMock = new Mock<IDbSet<TEntity>>();
dbSetMock.As<IDbAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator()));
dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(data.Provider));
dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.Expression).Returns(data.Expression);
dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.ElementType).Returns(data.ElementType);
dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.GetEnumerator()).Returns(data.GetEnumerator());
return dbSetMock;
}
In my actual handler method which is testing I have a line:
var attachment = await _context.Set<Attachment>()
.SingleAsync(x => x.Id == command.Id);
And when I run the test it fails and show me the message
Message: System.InvalidOperationException : The sequence does not contain a matching element.
But when I change the query in handler to be ListAsync() instead of SingleAsync() then the mock is setup correctly and returns me an empty list. But it does not work for the single element using SingleAsync().
EDIT:
Here is my full Handler method:
public async Task<Unit> Handle(DeleteAttachmentCommand command, CancellationToken cancellationToke = default(CancellationToken))
{
ValidateCommand(command);
var attachment = await _context.Set<Attachment>().SingleAsync(x => x.Id == command.Id);
attachment.IsDeleted = true;
await _context.SaveChangesAsync();
return Unit.Value;
}

SingleAsync() documentation in MSDN
Asynchronously returns the only element of a sequence, and throws an
exception if there is not exactly one element in the sequence.
Consider to use FirstOrDefault()or FirstOrDefaultAsync() method instead of SingleAsync(). Here and here are a link about that, this will not throw exception.

Related

Mock Azure File Share CreateAsync method in unit test

I have the following method, that I'm trying to write some unit tests
public async Task<ShareClient> CreateFileShare(string shareName)
{
if (!ShareNameValidator.IsValid(shareName))
{
throw new ArgumentException("Invalid share name.");
}
ShareClient shareFileClient = _shareServiceClient.GetShareClient(shareName.ToLowerInvariant());
if (await shareFileClient.ExistsAsync())
{
throw new ShareExistsException("Share already exists.");
}
await shareFileClient.CreateAsync();
return shareFileClient;
}
this is part of a helper class, and all the dependencies you're seeing will be available in run-time.
I have 3 nit tests for this. just putting signature, but it's quite straight forward
public async Task CreateFileShare_ShouldThrowException_WhenShareAlreadyExists()
public async Task CreateFileShare_ShouldThrowException_WhenInvalidShareName()
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()
these are xunit tests. first 2 are passing but last one is not. here is the code for last
[Fact]
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()
{
// Arrange
var responseMock = new Mock<Response<bool>>();
string validShareName = "valid-share-name";
responseMock.Setup(x => x.Value).Returns(false);
_shareClientMock.Setup(x => x.ExistsAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(responseMock.Object));
_shareServiceClientMock.Setup(x => x.GetShareClient(validShareName)).Returns(_shareClientMock.Object);
// Act
var createdShareClient = await _fileStorageHelper.CreateFileShare(validShareName);
// Assert
createdShareClient.Should().Be(_shareClientMock.Object);
_shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), It.IsAny<CancellationToken>()), Times.Once);
}
the error message:
Message: 
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x =>
x.CreateAsync(It.IsAny<ShareCreateOptions>(),
It.IsAny<CancellationToken>())
Performed invocations:
Mock<ShareClient:1> (x):
ShareClient.ExistsAsync(CancellationToken) => Mock<Response<bool>:1>
ShareClient.CreateAsync(null, null, CancellationToken)
Mock<Response<bool>:1>:
Response<bool>.Value
do you see any wrong set up in my mock?
here is another way I tried:
[Fact]
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()
{
// Arrange
string validShareName = "validsharename";
var responseMock = new Mock<Response<bool>>();
var shareInfoMock = new Mock<ShareInfo>();
var createResponseMock = new Mock<Response<ShareInfo>>();
ShareCreateOptions createOptions = new ShareCreateOptions
{
QuotaInGB = 100,
};
shareInfoMock.DefaultValue = DefaultValue.Mock;
responseMock.Setup(x => x.Value).Returns(false);
createResponseMock.Setup(x => x.Value).Returns(shareInfoMock.Object);
_shareClientMock.Setup(x => x.ExistsAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(responseMock.Object));
_shareClientMock.Setup(x => x.CreateAsync(createOptions, It.IsAny<CancellationToken>())).Returns(Task.FromResult(createResponseMock.Object));
_shareServiceClientMock.Setup(x => x.GetShareClient(validShareName)).Returns(_shareClientMock.Object);
// Act
var createdShareClient = await _fileStorageHelper.CreateFileShare(validShareName);
// Assert
createdShareClient.Should().Be(_shareClientMock.Object);
_shareClientMock.Verify(x => x.ExistsAsync(It.IsAny<CancellationToken>()), Times.Once());
_shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), It.IsAny<CancellationToken>()), Times.Once());
}
failing with the same error.
the problem is in verify part
instead of
_shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), It.IsAny<CancellationToken>()), Times.Once());
have this
_shareClientMock.Verify(x => x.CreateAsync(It.IsAny<Dictionary<string, string>>(), null, It.IsAny<CancellationToken>()), Times.Once());
the reason for this,as it is saying in the error message is that it calls another overloaded version of CreateAsync method.
credit to Nick :)

LINQ expression to replace foreach with multiple awaits

I am trying to determine if there is a linq expression equivalent of the following foreach statement below?
Two of the several solutions I've tried are commented with their results below the loop. I thought the first example or a variation would work but the type is always IEnumerable<Task> unless I call it synchronously which just feels apprehensive.
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync()
{
foreach(var company in await GetParticipatingCompaniesAsync())
{
settings.Add(new CompanySettings(company, await GetSyncDataAsync(company)));
}
// a's Type is IEnumerable<Task<CompanySetting>> and not IEnumerable<CompanySetting>
// var a = (await GetParticipatingCompaniesAsync())
// .Select(async x => new CompanySettings(x, await GetSyncDataAsync(x)));
// return a;
// b's Type is correct it's also synchronous. Does that matter?
// var b = (GetParticipatingCompaniesAsync()).Result
// .Select(x => new CompanySettings(x, GetSyncDataAsync(x).Result));
//return b;
return settings;
}
Signatures of the methods:
private async Task<IEnumerable<UpdoxCompany>> GetParticipatingCompaniesAsync();
private async Task<UpdoxSyncData> GetUpdoxSyncDataAsync(UpdoxCompany company);
You are close with your first attempt, you just need to await the tasks that are returned
async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync() {
var companies = await GetParticipatingCompaniesAsync();
var companySettings = companies.Select(async company =>
new CompanySettings(company, await GetSyncDataAsync(company))
);
return await Task.WhenAll(companySettings);
}
Yes, you can use Select to achieve the same result
Here is one way to do it:
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync()
{
var companies = await GetParticipatingCompaniesAsync();
var companySettingsTasks = companies.Select(async company =>
new CompanySettings(company, await GetSyncDataAsync(company)));
var settings = await Task.WhenAll(companySettingsTasks);
return settings;
}

Why when I use some queries in unit tests the created item doesn't load?

I'm using XUnit on the boilerplate (abp 6.0.0) and in the unit test example below I'm creating a "SwitchDto" and in the variable "isHere" I checked it and it was created.
[Fact]
public async Task Test_Example()
{
var switchDto = await _switchAppService.CreateAsync(SwitchTr1_Valid_Dto);
var id = switchDto.Id;
await UsingDbContextAsync(async context =>
{
var isHere = await context.Switch.FirstOrDefaultAsync(u => u.Id == id);
});
using var switchController = new SwitchController(_switchAppService);
using var switchInterfaceController = new SwitchInterfaceController(_switchInterfaceAppService);
switchDto = await _switchAppService.UpdateAsync(switchDto);
switchDto.Hostname.ShouldBe("switchHost");
await UsingDbContextAsync(async context =>
{
var _switch = await context.Switch.FirstOrDefaultAsync(u => u.Id == id);
_switch.ShouldNotBeNull();
_switch.SerialNumber.ShouldBe("5678");
});
}
But on "UpdateAsync" I get an error during a validation as it can't find the "switch" created in the method below:
public override async Task<SwitchDto> GetAsync(EntityDto<int> input)
{
CheckGetPermission();
Switch sw = await Repository.GetAllIncluding(i => i.TechnicalRoom)
.Include(i => i.SwitchManufacturerModel)
.ThenInclude(i => i.SwitchBrand)
.FirstOrDefaultAsync(x => x.Id == input.Id);
return MapToEntityDto(sw);
}
I already tried to execute the UpdateAsync method in the block UsingDbContextAsync.
The problem only happens during unit tests, if I run the application the same method brings the values normally.
Why when I use this queries above the created item doesn't load?

Why can't I setup CancellationToken?

I'm trying to write a test for a class and it seems that I can't setup the CheckLoginAsync method of mine. This CheckLoginAsync method has 2 parameters. The second parameter is an optional one and looks like this:
public async Task CheckLoginAsync(ISettings userSettings, CancellationToken? cancellationToken = null);
The setup code is the following:
backendFacadeMock = new Mock<BackendFacade>(MockBehavior.Strict);
backendFacadeMock
.Setup(bf => bf.Initialize())
.Returns(() => Task.FromResult(0));
backendFacadeMock
.Setup(bf => bf.CheckLoginAsync(It.IsAny<ISettings>(), It.IsAny<CancellationToken?>()))
.Returns(() => Task.FromResult(0));
nullBackendFacadeMock = new Mock<IBackendFacade>(MockBehavior.Strict);
backendFacadeFactoryMock = new Mock<IBackendFacadeFactory>(MockBehavior.Strict);
backendFacadeFactoryMock
.Setup(bf => bf.CreateBackendFacade())
.Returns(() => backendFacadeMock.Object);
backendFacadeFactoryMock
.Setup(bf => bf.CreateNullBackendFacade())
.Returns(() => nullBackendFacadeMock.Object);
The code that is being tested is the following:
private async Task<IBackendFacade> CreateBackendFacade()
{
IBackendFacade backendFacade = _backendFacadeFactory.CreateBackendFacade();
try
{
await backendFacade.CheckLoginAsync(_userSettings);
await backendFacade.Initialize();
}
catch (Exception e)
{
backendFacade = _backendFacadeFactory.CreateNullBackendFacade();
}
return backendFacade;
}
The CheckLoginAsync invocation in the code mentioned above throws the following exception:
IBackendFacade.CheckLoginAsync(Mock<de.cas.officeintegration.outlook.core.application.ISettings:00000001>.Object, null) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
The test itself:
[Test]
public async Task GetBackendFacadeAsync_NoException_ReturnsABackendFacadeObject()
{
var backendFacadeProvider = new BackendFacadeProvider(backendFacadeFactoryMock.Object, userSettingsMock.Object);
IBackendFacade backendFacade = await backendFacadeProvider.GetBackendFacadeAsync();
Assert.AreSame(backendFacadeMock.Object, backendFacade);
}
Thanks in advance!

How to Mock IMongoDatabase

I'm using Moq for mocking my objects in an ASP.NET Core project.
I want to mock the following IsConnection() method:
public Client(IMongoClient client)
{
_client = client;
}
public async Task<bool> IsConectionOk()
{
var pingCommand = new BsonDocument("ping", 1);
var mongoDb = _client.GetDatabase("Name");
var commandResult = await mongoDb.RunCommandAsync<BsonDocument>(pingCommand);
return commandResult != null;
}
As you see, there's only one injection, IMongoClient, so I need to mock this one. Now, I need to mock IMongoDatabase as well since the _client.GetDatabase returns me an IMongoDatabase which runs RunCommandAsync
this is my unit test:
[Fact]
public async Task IsConnectionOk_xxx_RunPing1Command()
{
var dbMock = new Mock<IMongoDatabase>();
var resultCommand = new BsonDocument("ok", 1);
dbMock.Setup(stub => stub.RunCommandAsync<BsonDocument>(It.IsAny<BsonDocument>(), It.IsAny<ReadPreference>(), It.IsAny<CancellationToken>())).ReturnsAsync(resultCommand);
var mongoClientMock = new Mock<IMongoClient>();
mongoClientMock.Setup(stub => stub.GetDatabase(It.IsAny<string>(), It.IsAny<MongoDatabaseSettings>())).Returns(dbMock.Object);
var client = new Client(mongoClientMock.Object);
var pingCommand = new BsonDocument("ping", 1);
//act
await client.IsConectionOk();
//assert
dbMock.Verify(mock => mock.RunCommandAsync<BsonDocument>( It.Is<BsonDocument>(x => x.Equals(pingCommand)) , It.IsAny<ReadPreference>() ,It.IsAny<CancellationToken>()));
}
You can see that I mocked a IMongoDatabase so my mongoClientMock can return it when the code is executing. When code is running, I have checked that mongoClientMock.GetDatabase() is returning a MongoDatabase (good until there), the problem is that when MongoDatabaseMock calls RunCommandAsync is not returning what I set up, it just returns null. I don't know what I could be missing here, any thoughts?
Things are a little tricky here.
Some background first.
According to documentation, IMongoDatabase.RunCommandAsync<TResult> is defined as
Task<TResult> RunCommandAsync<TResult>(
Command<TResult> command,
ReadPreference readPreference = null,
CancellationToken cancellationToken = null
)
Note the Command<TResult>, while in your code you pass a BsonDocument.
Luckily BsonDocument has an implicit conversion operator from BsonDocument to Command<TResult>
When a setup does not get what was configured it tends to return null. So you need to make sure that it is setup properly so that it performs the expected behavior.
[TestClass]
public class UnitTest1 {
[TestMethod]
public async Task _IsConnectionOk_xxx_RunPing1Command() {
var dbMock = new Mock<IMongoDatabase>();
var resultCommand = new BsonDocument("ok", 1);
dbMock
.Setup(stub => stub.RunCommandAsync<BsonDocument>(It.IsAny<Command<BsonDocument>>(), It.IsAny<ReadPreference>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(resultCommand)
.Verifiable();
var mongoClientMock = new Mock<IMongoClient>();
mongoClientMock
.Setup(stub => stub.GetDatabase(It.IsAny<string>(), It.IsAny<MongoDatabaseSettings>()))
.Returns(dbMock.Object);
var client = new Client(mongoClientMock.Object);
var pingCommand = new BsonDocument("ping", 1);
//act
var actual = await client.IsConectionOk();
//assert
Assert.IsTrue(actual);
dbMock.Verify();
}
}
Just found my problem, take a look at the next line:
dbMock.Setup(stub => stub.RunCommandAsync<BsonDocument>(It.IsAny<BsonDocument>(), It.IsAny<ReadPreference>(), It.IsAny<CancellationToken>())).ReturnsAsync(resultCommand);
turns out that RunCommandAsync first parameter is a Command<TResult> so the fix I needed was:
dbMock.Setup(stub => stub.RunCommandAsync<BsonDocument>(It.IsAny<Command<BsonDocument>>(), It.IsAny<ReadPreference>(), It.IsAny<CancellationToken>())).ReturnsAsync(anyResultCommand);
And problem solved!

Categories

Resources