Visual Studio 2019 Enterprise 16.9.4; Moq 4.16.1; xunit 2.4.1; net5.0
I'm trying to unit test my AlbumData.GetAlbumsAsync() method. I mock the SqlDataAccess layer which is making a call to the DB using Dapper in a generic method.
This is my setup. The mock is not working. In the AlbumData.GetAlbumsAsync() method the call to the mocked object (_sql.LoadDataAsync) returns null and output is set to null.
Can anyone tell me what I'm dong wrong?
SqlDataAccess.cs
public async Task<List<T>> LoadDataAsync<T, U>(string storedProcedure,
U parameters, string connectionStringName)
{
string connectionString = GetConnectionString(connectionStringName);
using (IDbConnection connection = new SqlConnection(connectionString))
{
IEnumerable<T> result = await connection.QueryAsync<T>(storedProcedure, parameters,
commandType: CommandType.StoredProcedure);
List<T> rows = result.ToList();
return rows;
}
}
AlbumData.cs
public class AlbumData : IAlbumData
{
private readonly ISqlDataAccess _sql;
public AlbumData(ISqlDataAccess sql)
{
_sql = sql;
}
public async Task<List<AlbumModel>> GetAlbumsAsync()
{
var output = await _sql.LoadDataAsync<AlbumModel, dynamic>
("dbo.spAlbum_GetAll", new { }, "AlbumConnection");
return output;
}
...
}
AlbumDataTest.cs
public class AlbumDataTest
{
private readonly List<AlbumModel> _albums = new()
{
new AlbumModel { Title = "Album1", AlbumId = 1 },
new AlbumModel { Title = "Album2", AlbumId = 2 },
new AlbumModel { Title = "Album3", AlbumId = 3 }
};
[Fact]
public async Task getAlbums_returns_multiple_records_test()
{
Mock<ISqlDataAccess> sqlDataAccessMock = new();
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>
(It.IsAny<string>(), new { }, It.IsAny<string>()))
.Returns(Task.FromResult(_albums));
AlbumData albumData = new AlbumData(sqlDataAccessMock.Object);
List<AlbumModel> actual = await albumData.GetAlbumsAsync();
Assert.True(actual.Count == 3);
}
...
}
UPDATE1:
Following #freeAll and #brent.reynolds suggestions I updated the test to use It.IsAny<string>()
Also updated #brent.reynolds fiddle to actually implement a unit test:
https://dotnetfiddle.net/nquthR
It all works in the fiddle but when I paste the exact same test into my AlbumDataTest it still returns null. Assert.Null(actual); passes, Assert.True(actual.Count == 3); fails.
UPDATE2:
I've posted a project with the failing test to https://github.com/PerProjBackup/Failing-Mock.
If you run the API.Library.ConsoleTests project the Mock works. If you run the tests in the API.Library.Tests project with the Test Explorer the Mock fails.
#brent.reynolds was able to get the Mock to work by changing the dynamic generic to object. Now trying to debug the dynamic issue.
UPDATE3:
If I move the AlbumData class into the same project as the AllbumDataTest class the mock works (using dynamic) returning the list with three objects. But when the AlbumData class is in a separate project (as it would be in the real world) the mock returns null.
I've updated the https://github.com/PerProjBackup/Failing-Mock repository. I deleted the console app and created a Failing and Passing folder with the two scenarios.
Why would the class that the mock is being passed to being in a different project make the mock fail?
UPDATE4:
See brent.reynolds accepted answer and my comment there. Issue was the use of an anonymous object in the the mock setup. I've deleted the Failing-Mock repository and the dotnetfiddle.
It might be related to the async method. According to the documentation for async methods, You can either do
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>())
.Result)
.Returns(_albums);
or
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>()))
.ReturnsAsync(_albums);
Edited:
Try adding It.IsAny<object>() to the setup:
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, object>(
It.IsAny<string>(),
It.IsAny<object>(),
It.IsAny<string>()))
.Returns(Task.FromResult(_albums));
and changing the type parameter in GetAlbumsAsync() to:
var output = await _sql.LoadDataAsync<AlbumModel, object>(
"dbo.spAlbum_GetAll",
new { },
"AlbumConnection");
OP Note/Summary:
The use of an anonymous object new {} in the mock setup is the central issue. It works when the test class and class being tested are in the same project but not when they are in separate projects since it cannot then be reused. It.IsAny<dynamic>() will not work because the compiler forbids dynamic inside LINQ expression trees. brent.reynolds use of object resolves the issue.
Use It.IsAny<string>() instead of the empty strings
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>
(It.IsAny<string>(), new { }, It.IsAny<string>())).Returns(Task.FromResult(_albums));
Note: you can't use It.IsAny<T>() on dynamic objects
Related
C# method where I am using IMapper interface
foreach (var req in listRequestMasters)
{
var customReq = _mapper.Map<GridModel>(req);
}
by below line of code getting success assertion but not as expected result, the one record getting twice
_mockMapper.Setup(x => x.Map<GridModel>(It.IsAny<RequestMaster>())).Returns(requestGridModelMockData.FirstOrDefault());
and by below line of code getting
Message:
Moq.MockException : IMapperBase.Map(RequestMaster)
invocation failed with mock behavior Strict. All invocations on the
mock must have a corresponding setup.
_mockMapper.Setup(x => x.Map<List<GridModel>>(It.IsAny<RequestMaster>())).Returns(requestGridModelMockData);
remaining code
var result = await Service.GetHistoryRequestDetails(historyVMMockData);
Assert.Equal(JsonConvert.SerializeObject(requestGridModelMockData), JsonConvert.SerializeObject(result));
AutoMapper is a library that is already well tested on its own. So you should use the AutoMapper library also in your tests and not mock it, because you want to test your code and not others library code (or do you have also self written tests for ASP core, EF or something similar?).
In most environments you manage your mapping within individual Profiles. If not, you should do so and create within your test a fresh mapper (preferable in constructor) and use it as usual.
For example you have some DTOs:
public class Source
{
public string Name { get; set; }
}
public class Destination
{
public string Name { get; set; }
}
And somewhere else you defined the AutoMapper mappings:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Source, Destination>();
}
}
Then within your test class you do something like this:
public class MyTests
{
private readonly IMapper mapper;
public MyTests()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MyProfile>());
var mapper = config.CreateMapper();
}
[Fact]
public void TestSomething()
{
var source = new Source { Name = "foo" };
var sut = new SystemUnderTest(mapper);
var result = sut.DoSomething(source);
var expected = mapper.Map<Dest>(source);
Assert.Equal(expected, result, DestinationComparer.Default);
}
}
As you can see, the system under test receives the real mapper (even if you just care for an IMapper interface). You don't create a mock, stub, puppet, you name it for 3rd party libraries. You use the real implemenations, because you write tests for your code.
Maybe it makes sense to write tests checking if a 3rd party library works as expected, which helps narrow problems down when upgrading a library and still behaves the same. But these tests don't use anything of your code and just tests some library function:
[Fact]
public void CheckMapperMapsCorrectly()
{
var source = new Source { Name = "Foo" };
var dest = mapper.Map<Destination>(source);
Assert.Equal(source.Name, dest.Name);
}
But that is something different and fully optional, depending on how much you trust each 3rd party library or makes sense if you already fall into that pit on an upgrade of a 3rd party library to avoid the same problem on next update.
I have created an abstract class that implements Polly that I want to write unit tests for.
In one of my tests I want to test how my method handles certain values of PolicyResult.FinalException.
Because the returned PolicyResult is null I get a NullReferenceException when evaluating result.FinalException
How do I mock the returned result?
What I have so far:
public class AbstractRestClientTest
{
private AbstractRestClient _sut;
private Mock<IRestRequestFactory> _requestFactoryMock;
private Mock<IRestClientFactory> _restClientfactoryMock;
private Mock<IPollyPolicyFactory> _policyFactoryMock;
private Mock<IAsyncPolicy> _policyMock;
private const string DUMMY_URL = "http://dosomething.com/getmesomething";
[SetUp]
public void SetUp()
{
_requestFactoryMock = new Mock<IRestRequestFactory>();
_restClientfactoryMock = new Mock<IRestClientFactory>();
_policyFactoryMock = new Mock<IPollyPolicyFactory>();
var settings = new MockSettings();
_policyMock = new Mock<IAsyncPolicy>();
_policyFactoryMock.Setup(mock =>
mock.CreateAsyncResiliencePolicy(settings))
.Returns(_policyMock.Object);
_sut = new MockRestClient(settings, _restClientfactoryMock.Object,
_policyFactoryMock.Object,
_requestFactoryMock.Object);
}
}
public class MockRestClient : AbstractRestClient
{
public MockRestClient(RestSettings settings, IRestClientFactory restClientFactory, IPollyPolicyFactory pollyPolicyFactory,
IRestRequestFactory requestFactory) : base(settings, restClientFactory, pollyPolicyFactory, requestFactory) {
}
}
public class MockSettings : RestSettings
{
public override string Naam => "TestSettings";
}
------------------ EDIT 1 --------------------------------
With Nkosi's comment I got a little bit further but still PolicyResult returned by _policy.ExecuteAndCaptureAsync is null. This leads me to believe that there is something wrong in the way that I mock that method.
I changed my test to the following but still it returns `null``:
[Test]
public async Task HandleRequest_IfFinalExceptionNotNull_ThenThrowsException()
{
var mockResult = new Mock<IRestResponse<int>>();
PolicyResult<IRestResponse<int>> result = PolicyResult<IRestResponse<int>>.Failure(mockResult.Object, new Context());
//Is the following mock correctly setup?
_policyMock.Setup(mock => mock.ExecuteAndCaptureAsync(It.IsAny<Func<Task<IRestResponse<int>>>>()))
.ReturnsAsync(result);
var url = new Url(DUMMY_URL);
Assert.ThrowsAsync<Exception>(() => _sut.GetResult<int>(url));
}
I evaluated the parameters needed for ExecuteAndCapture and changed my setup for this method accordingly, what am I doing wrong?
Based on the publicly available source code on GitHub, there really is no need to mock that class. While it does have an internal constructor, static factory methods exist that should allow for the creation of your desired instance
For example
Context context = //...created as needed
PolicyResult<TestResponse> result = PolicyResult<TestResponse>.Failure(..., context);
Choose the right combination to satisfy the expected result in your test.
The issue was I was mocking the wrong version of ExecuteAndCaptureAsync, I needed to mock the method with the following signature:
`Task<PolicyResult> ExecuteAndCaptureAsync(Func<CancellationToken, Task> action, CancellationToken cancellationToken);`
So after I changes my SetUp accordingly the test succeeded:
[Test]
public async Task HandleRequest_IfFinalExceptionNotNull_ThenThrowsException()
{
var mockResult = new Mock<IRestResponse<int>>();
PolicyResult<IRestResponse<int>> result = PolicyResult<IRestResponse<int>>.Failure(mockResult.Object, new Context());
_policyMock.Setup(mock => mock.ExecuteAndCaptureAsync(
It.IsAny<Func<CancellationToken, Task<IRestResponse<int>>>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(result);
var url = new Url(DUMMY_URL);
Assert.ThrowsAsync<Exception>(() => _sut.GetResultaat(url, new CancellationToken()));
}
Scenario:
We are in the lovely scenario of a terrible data source that requires an arcane syntax. We have built our "repository" layer to translate simple parameters (primitive values) into the correct syntax for the destination.
We would like to unit test that:
The correct filters have been applied (can be done by checking the string that the repo's helper methods create)
The (mocked) remote data source is called exactly once
The (mocked) data we have defined the remote data source as returning is passed back as the return value when we call the repo.
For example
var expectedReturn = new List<Product> { new Product { StockNumber = "123" } };
provider.Setup(x => x.Run(It.IsAny<Func<IRemoteClient, Task<List<Product>>>>(),
It.IsAny<string>())).ReturnsAsync(expectedReturn);
Moq is failing on the Setup line with a NotSupportedException. I've read probably a dozen or more SO posts and can't find out why it doesn't work.
In normal usage, the Repo will use something like:
provider.Run(x => x.GetAsync<List<Product>>(requestBuilder.Request), "foo")
Definition of run in provider interface:
Task<T> Run<T>(Func<IRemoteClient, Task<T>> action, string name);
Since the requestBuilder is injected as well, we can easily evaluate that the request is built correctly as far as the number and type of parameters, but we can't run the test at all because the Mock call fails the setup and so we never get there.
I am using Moq 4.9.0 and have tested this both on .NET Core 2.1, as well as inside LINQPad using the .NET Framework. It compiles and runs for me without any problems. I am able to run the mock setup, and I am also able to call the mocked method on the mock object, and retrieve the expected return result.
The following is my test code:
class Program
{
static void Main(string[] args)
{
var expectedReturn = new List<Product> { new Product { StockNumber = "123" } };
var provider = new Mock<IProvider>();
provider
.Setup(x => x.Run(
It.IsAny<Func<IRemoteClient, Task<List<Product>>>>(),
It.IsAny<string>()))
.ReturnsAsync(expectedReturn);
var result = provider.Object.Run(client => client.GetAsync<List<Product>>(null), "foo");
Console.WriteLine(result.Result[0].StockNumber);
}
}
public interface IProvider
{
Task<T> Run<T>(Func<IRemoteClient, Task<T>> action, string name);
}
public interface IRemoteClient
{
Task<T> GetAsync<T>(object request);
}
public class Product
{
public string StockNumber { get; set; }
}
I'm using Microsoft Fakes to Shim an async method that invokes another method to get an implemented DbContext. Because database connection string is not supplied in the Unit Test while the method being invoked inside the async method needs it. Shim will not only skip the method that uses the connection string, but returns a customizable DbContext.
Here is the aysnc method implementation:
public async Task<AccountDataDataContext> GetAccountDataInstance(int accountId)
{
var account = await this.Accounts.FindAsync(accountId);
return AccountDataDataContext.GetInstance(account.AccountDataConnectionString);
}
However, I'm not familiar with Shim async method. What I did look like this:
ConfigurationEntities.Fakes.ShimConfigurationDataContext.AllInstances.GetAccountDataInstanceInt32NullableOfInt32 = (x, y, z) => new Task<AccountDataEntities.AccountDataDataContext>(() =>
{
return new SampleContext();// This is the fake context I created for replacing the AccountDataDataContext.
});
And SampleContext is implementing AccountDataDataContext as follows:
public class SampleContext: AccountDataDataContext
{
public SampleContext()
{
this.Samples = new TestDbSet<Sample>();
var data = new AccountDataRepository();
foreach (var item in data.GetFakeSamples())
{
this.Samples.Add(item);
}
}
}
Below is the code snippet for the test case:
[TestMethod]
public async Task SampleTest()
{
using (ShimsContext.Create())
{
//Arrange
SamplesController controller = ArrangeHelper(1);// This invokes the Shim code pasted in the second block and returns SamplesController object in this test class
var accountId = 1;
var serviceId = 2;
//Act
var response = await controller.GetSamples(accountId, serviceId);// The async method is invoked in the GetSamples(int32, int32) method.
var result = response.ToList();
//Assert
Assert.AreEqual(1, result.Count);
Assert.AreEqual("body 2", result[0].Body);
}
}
As the result, my test case is running forever. I think I might write the Shim lamdas expression completely wrong.
Any suggestion? Thank you.
You don't want to return a new Task. In fact, you should never, ever use the Task constructor. As I describe on my blog, it has no valid use cases at all.
Instead, use Task.FromResult:
ConfigurationEntities.Fakes.ShimConfigurationDataContext.AllInstances.GetAccountDataInstanceInt32NullableOfInt32 =
(x, y, z) => Task.FromResult(new SampleContext());
Task also has several other From* methods that are useful for unit testing (e.g., Task.FromException).
I'd like to use NSubstitute to unit test Entity Framework 6.x by mocking DbSet. Fortunately, Scott Xu provides a good unit testing library, EntityFramework.Testing.Moq using Moq. So, I modified his code to be suitable for NSubstitute and it's been looking good so far, until I wanted to test DbSet<T>.Add(), DbSet<T>.Remove() methods. Here's my code bits:
public static class NSubstituteDbSetExtensions
{
public static DbSet<TEntity> SetupData<TEntity>(this DbSet<TEntity> dbset, ICollection<TEntity> data = null, Func<object[], TEntity> find = null) where TEntity : class
{
data = data ?? new List<TEntity>();
find = find ?? (o => null);
var query = new InMemoryAsyncQueryable<TEntity>(data.AsQueryable());
((IQueryable<TEntity>)dbset).Provider.Returns(query.Provider);
((IQueryable<TEntity>)dbset).Expression.Returns(query.Expression);
((IQueryable<TEntity>)dbset).ElementType.Returns(query.ElementType);
((IQueryable<TEntity>)dbset).GetEnumerator().Returns(query.GetEnumerator());
#if !NET40
((IDbAsyncEnumerable<TEntity>)dbset).GetAsyncEnumerator().Returns(new InMemoryDbAsyncEnumerator<TEntity>(query.GetEnumerator()));
((IQueryable<TEntity>)dbset).Provider.Returns(query.Provider);
#endif
...
dbset.Remove(Arg.Do<TEntity>(entity =>
{
data.Remove(entity);
dbset.SetupData(data, find);
}));
...
dbset.Add(Arg.Do<TEntity>(entity =>
{
data.Add(entity);
dbset.SetupData(data, find);
});
...
return dbset;
}
}
And I created a test method like:
[TestClass]
public class ManipulationTests
{
[TestMethod]
public void Can_remove_set()
{
var blog = new Blog();
var data = new List<Blog> { blog };
var set = Substitute.For<DbSet<Blog>, IQueryable<Blog>, IDbAsyncEnumerable<Blog>>()
.SetupData(data);
set.Remove(blog);
var result = set.ToList();
Assert.AreEqual(0, result.Count);
}
}
public class Blog
{
...
}
The issue arises when the test method calls set.Remove(blog). It throws an InvalidOperationException with error message of
Collection was modified; enumeration operation may not execute.
This is because the fake data object has been modified when the set.Remove(blog) method is called. However, the original Scott's way using Moq doesn't result in the issue.
Therefore, I wrapped the set.Remove(blog) method with a try ... catch (InvalidOperationException ex) block and let the catch block do nothing, then the test doesn't throw an exception (of course) and does get passed as expected.
I know this is not the solution, but how can I achieve my goal to unit test DbSet<T>.Add() and DbSet<T>.Remove() methods?
What's happening here?
set.Remove(blog); - this calls the previously configured lambda.
data.Remove(entity); - The item is removed from the list.
dbset.SetupData(data, find); - We call SetupData again, to reconfigure the Substitute with the new list.
SetupData runs...
In there, dbSetup.Remove is being called, in order to reconfigure what happens when Remove is called next time.
Okay, we have a problem here. dtSetup.Remove(Arg.Do<T.... doesn't reconfigure anything, it rather adds a behavior to the Substitute's internal list of things that should happen when you call Remove. So we're currently running the previously configured Remove action (1) and at the same time, down the stack, we're adding an action to the list (5). When the stack returns and the iterator looks for the next action to call, the underlying list of mocked actions has changed. Iterators don't like changes.
This leads to the conclusion: We can't modify what a Substitute does while one of its mocked actions is running. If you think about it, nobody who reads your test would assume this to happen, so you shouldn't do this at all.
How can we fix it?
public static DbSet<TEntity> SetupData<TEntity>(
this DbSet<TEntity> dbset,
ICollection<TEntity> data = null,
Func<object[], TEntity> find = null) where TEntity : class
{
data = data ?? new List<TEntity>();
find = find ?? (o => null);
Func<IQueryable<TEntity>> getQuery = () => new InMemoryAsyncQueryable<TEntity>(data.AsQueryable());
((IQueryable<TEntity>) dbset).Provider.Returns(info => getQuery().Provider);
((IQueryable<TEntity>) dbset).Expression.Returns(info => getQuery().Expression);
((IQueryable<TEntity>) dbset).ElementType.Returns(info => getQuery().ElementType);
((IQueryable<TEntity>) dbset).GetEnumerator().Returns(info => getQuery().GetEnumerator());
#if !NET40
((IDbAsyncEnumerable<TEntity>) dbset).GetAsyncEnumerator()
.Returns(info => new InMemoryDbAsyncEnumerator<TEntity>(getQuery().GetEnumerator()));
((IQueryable<TEntity>) dbset).Provider.Returns(info => getQuery().Provider);
#endif
dbset.Remove(Arg.Do<TEntity>(entity => data.Remove(entity)));
dbset.Add(Arg.Do<TEntity>(entity => data.Add(entity)));
return dbset;
}
The getQuery lambda creates a new query. It always uses the captured list data.
All .Returns configuration calls use a lambda. In there, we create a new query instance and delegate our call there.
Remove and Add only modify our captured list. We don't have to reconfigure our Substitute, because every call reevaluates the query using the lambda expressions.
While I really like NSubstitute, I would strongly recommend looking into Effort, the Entity Framework Unit Testing Tool.
You would use it like this:
// DbContext needs additional constructor:
public class MyDbContext : DbContext
{
public MyDbContext(DbConnection connection)
: base(connection, true)
{
}
}
// Usage:
DbConnection connection = Effort.DbConnectionFactory.CreateTransient();
MyDbContext context = new MyDbContext(connection);
And there you have an actual DbContext that you can use with everything that Entity Framework gives you, including migrations, using a fast in-memory-database.