Did 9.1.3 FluentValidation update ruin validators mocks? - c#

I updated to 9.1.3 version of the package and now my validator mocks do not work.
Somehow code runs no matter whether .Validate() returns true or false.
Here is the code for the validator mock:
validatorMock
.Setup(x => x.Validate(It.IsAny<IValidationContext>()).IsValid)
.Returns(false);
Assert.Throws<ValidationException>(() => command.Execute(request), "Position field validation error");
repositoryMock.Verify(repository => repository.EditPosition(It.IsAny<DbPosition>()), Times.Never);
And here is the test failing:
Message:
Position field validation error
Expected: <FluentValidation.ValidationException>
But was: null
Validator.cs:
public class SampleValidator : AbstractValidator<Position>
{
public SampleValidator()
{
RuleFor(position => position.Name)
.NotEmpty()
.MaximumLength(80)
.WithMessage("Position name is too long");
RuleFor(position => position.Description)
.NotEmpty()
.MaximumLength(350)
.WithMessage("Position description is too long");
}
}
Dependency Injection:
services.AddTransient<IValidator<Position>, SampleValidator>();
Usage:
public class SampleCommand : ISampleCommand
{
private readonly IValidator<Position> validator;
private readonly ISampleRepository repository;
private readonly IMapper<Position, DbPosition> mapper;
public SampleCommand(
[FromServices] IValidator<Position> validator,
[FromServices] ISampleRepository repository,
[FromServices] IMapper<Position, DbPosition> mapper)
{
this.validator = validator;
this.repository = repository;
this.mapper = mapper;
}
public bool Execute(Position request)
{
validator.ValidateAndThrow(request);
var position = mapper.Map(request);
return repository.EditPosition(position);
}
}
Validator Mocks in test:
private Mock<IValidator<EditPositionRequest>> validatorMock;
...
validatorMock = new Mock<IValidator<Position>>();
UPDATE
Before the update, all tests were running perfectly. Now they're ruined and I have to install the previous version.

Expanding on my comment:
Yes, 9.1 changed how throwing validation exceptions is handled.
Some context:
Validator classes return a ValidationResult with an IsValid boolean property. The ValidateAndThrow extension method checks this property, and throws an exception if IsValid is false. If you mocked the validator, you could still use the "real" ValidateAndThrow extension method on your mock to throw an exception if your mock returned an invalid validation result.
In FluentValidation 9.1, the logic for throwing the exception was moved out of the extension method and into the validator class itself, in the RaiseValidationException. This was done so that the logic for throwing the exception could be customized (by overriding this method) which couldn't be done before when it was an extension method.
// This is the ValidateAndThrow method definition versions older than 9.1
public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance) {
var result = validator.Validate(instance);
if (!result.IsValid) {
throw new ValidationException(result.Errors);
}
}
// This is the ValidateAndThrowMethod in 9.1 and newer
public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance) {
validator.Validate(instance, options => {
options.ThrowOnFailures();
});
}
For runtime use, this doesn't make a difference - the exception is still thrown (unless you've overridden the method to prevent this).
However, this had the side effect that if you were relying on the exception being thrown by the extension method rather than the validator, this will have an undesirable result. This is really only the case when mocking the validator. Now when you create a mock, the exception won't be thrown because the mock doesn't behave as a real validator.
My recommendation with FluentValidation has always been "don't mock validators", instead treat them as black boxes and supply real validator instances with valid/invalid input for testing purposes - this leads to much less brittle tests in the long run. However, I'm also aware that it may not be possible to rewrite your tests in this way if you already have lots of them.
As a workaround, you can mock the overload of Validate that takes a ValidationContext and check the context for the ThrowOnFailures property, and have your mock throw the exception if this is set to true.
However, be aware that if you do this you could run into a situation where your mock behaves one way and the real validator behaves differently (if its RaiseValidationException message has been overridden).
As this is a breaking change, shouldn't it have been made in a major version? Ideally yes, this was my bad as I didn't foresee this particular use case.
Edit: Here's an example of creating a mock that checks the ThrowOnFailures property. The example uses the Moq library, but the same concept will apply to other mocking libraries too.
private static Mock<IValidator<T>> CreateFailingMockValidator<T>() {
var mockValidator = new Mock<IValidator<T>>();
var failureResult = new ValidationResult(new List<ValidationFailure>() {
new ValidationFailure("Foo", "Bar")
});
// Setup the Validate/ValidateAsync overloads that take an instance.
// These will never throw exceptions.
mockValidator.Setup(p => p.Validate(It.IsAny<T>()))
.Returns(failureResult).Verifiable();
mockValidator.Setup(p => p.ValidateAsync(It.IsAny<T>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(failureResult);
// Setup the Validate/ValidateAsync overloads that take a context.
// This is the method called by ValidateAndThrow, so will potentially support throwing the exception.
// Setup method invocations for with an exception and without.
mockValidator.Setup(p => p.Validate(It.Is<ValidationContext<T>>(context => context.ThrowOnFailures)))
.Throws(new ValidationException(failureResult.Errors));
mockValidator.Setup(p => p.ValidateAsync(It.Is<ValidationContext<T>>(context => context.ThrowOnFailures), It.IsAny<CancellationToken>()))
.Throws(new ValidationException(failureResult.Errors));
// If ThrowOnFailures is false, return the result.
mockValidator.Setup(p => p.Validate(It.Is<ValidationContext<T>>(context => !context.ThrowOnFailures)))
.Returns(failureResult).Verifiable();
mockValidator.Setup(p => p.ValidateAsync(It.Is<ValidationContext<T>>(context => !context.ThrowOnFailures), It.IsAny<CancellationToken>()))
.ReturnsAsync(failureResult);
return mockValidator;
}

Related

How to mock FluentValidation ValidationResults in test

This is the method that I want to test:
public async Task<object> CreateExpense(CreateExpenseCommand command)
{
var validationResults = await _validator.ValidateAsync(command);
if (!validationResults.IsValid)
{
return validationResults.Errors.First().ToString();
}
//more code that is irrelevant for this post
}
To test this, I need to mock _validatior, which is defined as private readonly IValidator<CreateExpenseCommand> _validator; and is used through constructor injection.
I am using AutoFixture with AutoMoqCustomizations and Moq for mocking. Maybe I should use Moq exclusively?
This is what I try to do in the test:
[Fact]
public async Task CreateExpense_Success()
{
//Arrange
var service = _fixture.Fixture.Freeze<Mock<IValidator<CreateExpenseCommand>>>();
service.Setup(x => x.Validate((CreateExpenseCommand)It.IsAny<IValidator<CreateExpenseCommand>>())).Returns(It.IsAny<ValidationResult>);
//more code that is irrelevant for this post
}
However, this results in error:
System.NullReferenceException: 'Object reference not set to instance of an object'.
The error is pretty self-explanatory, but I don't know how to mock correctly.
Image of error:
You need to return an object when doing the setup:
service.Setup(x => x.Validate(It.IsAny<IValidator<CreateExpenseCommand>>()))
.Returns(<*1>);
*1 - Here return the object that you wish to be returned when you call ValidateAsync function. Don't do It.IsAny as it returns null which causes the NullReferenceException.
Also, you need to add virtual to the ValidateAsync method in order to let it be overridable.

How to verify ILogger<T>.Log extension method has been called using Moq?

I created a xUnit project to test this sample code
public class ClassToTest
{
private readonly ILogger<ClassToTest> _logger;
public ClassToTest(ILogger<ClassToTest> logger)
{
_logger = logger;
}
public void Foo() => _logger.LogError(string.Empty);
}
I installed Moq to create a mock instance for the logger
public class ClassToTestTests
{
private readonly ClassToTest _classToTest;
private readonly Mock<ILogger<ClassToTest>> _loggerMock;
public ClassToTestTests()
{
_loggerMock = new Mock<ILogger<ClassToTest>>();
_classToTest = new ClassToTest(_loggerMock.Object);
}
[Fact]
public void TestFoo()
{
_classToTest.Foo();
_loggerMock.Verify(logger => logger.LogError(It.IsAny<string>()), Times.Once);
}
}
When running the tests I get this error message
System.NotSupportedException: Unsupported expression: logger =>
logger.LogError(It.IsAny(), new[] { })
System.NotSupportedException Unsupported expression: logger =>
logger.LogError(It.IsAny(), new[] { }) Extension methods
(here: LoggerExtensions.LogError) may not be used in setup /
verification expressions.
After some research I know that all the log methods are just extension methods. Moq is not able to setup extension methods.
I would like to avoid installing additional third party packages for this problem. Are there any solutions to make the test pass?
You can't mock extension methods.
Instead of mocking
logger.LogError(...)
You need to mock the interface method
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
LogError actually calls that interface method like this
logger.Log(LogLevel.Error, 0, new FormattedLogValues(message, args), null, (state, ex) => state.ToString());
So you need to mock
_loggerMock.Verify(logger => logger.Log(It.Is(LogLevel.Error), It.Is(0), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<TState, Exception, string>>()), Times.Once);
Disclaimer I didn't verify the code
Edit after the comment from pinkfloydx33, I set up a test example in .net50 and came to the following answer
With the most recent framework the FormattedLogValues class has been made internal. So you can't use this with the generic Moq.It members. But Moq has an alternate way to do this (this answer also mentioned the solution)
For a call to the logger like this
_logger.LogError("myMessage");
You need to verify like this
_loggerMock.Verify(logger => logger.Log(
It.Is<LogLevel>(logLevel => logLevel == LogLevel.Error),
It.Is<EventId>(eventId => eventId.Id == 0),
It.Is<It.IsAnyType>((#object, #type) => #object.ToString() == "myMessage" && #type.Name == "FormattedLogValues"),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
You use the It.IsAnyType for types where you don't have access to. And if you need to restrict the verification you can add a func<object, type> to check if the type is what you expect or cast it to the public interface and validate any public members it has.
When you work with a string message and some parameters you need to cast the object of type FormattedLogValues to interface IReadOnlyList<KeyValuePair<string, object?>> and verify the string/values of the different parameters.
I found the answer Sergio Moreno posted in Git here worked for me:
mock.Verify(
x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v,t) => true),
It.IsAny<Exception>(),
It.Is<Func<It.IsAnyType, Exception, string>>((v,t) => true))

Unit testing a controller throwing exception

I have a controller with the following signature:
public CustomerTypeController(
IHttpContextAccessor accessor,
IPrincipalProvider provider,
IMapper mapper,
ILogger<CustomerTypeController> logger,
ICustomerTypeService customerTypeService)
{ }
For now my Theory looks like this:
[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
IFixture fixture,
[Frozen] Mock<ICustomerTypeService> service,
CustomerTypeController sut)
{
//Arrange
var items = fixture.CreateMany<Model.CustomerType>(3).ToList();
//Act
var result = sut.Get(1);
//Assert
Assert.IsType<OkResult>(result);
}
When I run this test as-is, I get the following exception:
AutoFixture.ObjectCreationExceptionWithPath : AutoFixture was unable to create an instance from Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo because creation unexpectedly failed with exception. Please refer to the inner exception to investigate the root cause of the failure.
Inner exception messages:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
System.ArgumentException: The type 'System.Object' must implement 'Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder' to be used as a model binder. (Parameter 'value')
What am I doing wrong, and how do I solve the problem?
TL;DR
You should decorate the controller parameter in your test method with the [NoAutoProperties] attribute.
[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
IFixture fixture,
[Frozen] Mock<ICustomerTypeService> service,
[NoAutoProperties] CustomerTypeController sut)
{
//Arrange
var items = fixture.CreateMany<Model.CustomerType>(3).ToList();
//Act
var result = sut.Get(1);
//Assert
Assert.IsType<OkResult>(result);
}
Update
Now that I know the AutoFixture code-base a little better, I wanted to understand why does this actually fix the issue.
The Greedy attribute normally instructs AutoFixture to use the constructor with the largest number of parameters, which should have nothing to do with the fix.
As the error message states, the exception occurs when a property is being set and the property is expecting a value that implements IModelBinder. The origin of the error is the BinderType property of the BindingInfo class, which is of type System.Type. By default AutoFixture will resolve Type as System.Object, which explains the error message.
When the Greedy attribute is applied, this customizes AutoFixture to create an instance of the property type, using a custom factory. The resulting builder graph node, (likely by accident) skips setting any properties, on the created instance.
Taking this into consideration, a more fitting resolution should be a using the NoAutoProperties attribute. This will explicitly instruct AutoFixture to ignore all auto-properties in the decorated type, but will leave the constructor query as "modest".
Since adding the attribute everywhere might get annoying and tedious, I suggest customizing AutoFixture to ignore all properties from ControllerBase, in the domain customization. Also in case you're using property injection, this will allow AutoFixture to instantiate the controller properties.
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(() => new Fixture().Customize(
new CompositeCustomization(
new AutoMoqCustomization(),
new AspNetCustomization())))
{
}
}
public class AspNetCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new ControllerBasePropertyOmitter());
}
}
public class ControllerBasePropertyOmitter : Omitter
{
public ControllerBasePropertyOmitter()
: base(new OrRequestSpecification(GetPropertySpecifications()))
{
}
private static IEnumerable<IRequestSpecification> GetPropertySpecifications()
{
return typeof(ControllerBase).GetProperties().Where(x => x.CanWrite)
.Select(x => new PropertySpecification(x.PropertyType, x.Name));
}
}
If you need the properties in ControllerBase for some reason then just instruct AutoFixture how to properly create BindingInfo instances.
Original answer
You should decorate the controller parameter in your test method with the [Greedy] attribute.
[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
IFixture fixture,
[Frozen] Mock<ICustomerTypeService> service,
[Greedy] CustomerTypeController sut)
{
//Arrange
var items = fixture.CreateMany<Model.CustomerType>(3).ToList();
//Act
var result = sut.Get(1);
//Assert
Assert.IsType<OkResult>(result);
}

How do I provide ILogger<T> in my unit tests of .NET Core code?

Given a class with a constructor signature of
public Foo(ILogger<Foo> logger) {
// ...
}
that I want to test, I need some way to provide an ILogger<Foo> in the test. It's been asked before, but the only answer then was to set up a full-blown service registry, configure logging and resolve the logger from there. This seems very overkill to me.
Is there a simple way to provide an implementation of ILogger<T> for testing purposes?
Note: it doesn't have to actually log anything - just not blow up when the subject under test tries to log something.
Starting from dotnet core 2.0 there's a generic NullLogger<T> class available:
var foo = new Foo(NullLogger<Foo>.Instance);
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger-1?view=aspnetcore-2.1 (docs)
https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/NullLoggerOfT.cs (source)
Or if you need it as part of your services:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nullloggerfactory?view=aspnetcore-2.1 (docs)
You can create an instance of ILogger<Foo> using NullLoggerFactory as the factory.
Consider the following controller:
public abstract class Foo: Controller
{
public Foo(ILogger<Foo> logger) {
Logger = logger;
}
public ILogger Logger { get; private set; }
}
A sample unit test could be:
[TestMethod]
public void FooConstructorUnitTest()
{
// Arrange
ILogger<FooController> logger = new Logger<FooController>(new NullLoggerFactory());
// Act
FooController target = new FooController(logger);
// Assert
Assert.AreSame(logger, target.Logger);
}
If you use generic logger (ILogger<>) in your classes those instances are generated from IServiceProvider you should register generic NullLogger<> on service provider as below. Not important what you use generic type T in ILogger<>
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
You have two options:
Create empty implementation of ILogger<Foo> by hand and pass an instance of it to ctor.
Create same empty implementation on the fly using some mocking framework like Moq, NSubstitute, etc.
You could inject ILoggerFactory instead and then create the logger
public Foo(ILoggerFactory loggerFactory) {
logger = loggerFactory.CreateLogger<Foo>();
// ...
}
At startup you need to add the NullLoggerFactory service of course:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>()
From the docs for ILogger<T> (emphasis mine):
A generic interface for logging where the category name is derived from the specified TCategoryName type name. Generally used to enable activation of a named ILogger from dependency injection.
So one option would be to change the implementation of the Foo method to take a plain ILogger and use the NullLogger implementation.
You should use the Null Object Pattern. This has two advantages for you: 1) you can get your tests up and running quickly and they won't "blow up", and 2) anyone will be able to use your class without supplying a logger. Just use NullLogger.Instance, or NullLoggerFactory.Instance.
However, you should use a mocking framework to verify that log calls get made. Here is some sample code with Moq.
[TestMethod]
public void TestLogError()
{
var recordId = new Guid("0b88ae00-7889-414a-aa26-18f206470001");
_logTest.ProcessWithException(recordId);
_loggerMock.Verify
(
l => l.Log
(
//Check the severity level
LogLevel.Error,
//This may or may not be relevant to your scenario
It.IsAny<EventId>(),
//This is the magical Moq code that exposes internal log processing from the extension methods
It.Is<It.IsAnyType>((state, t) =>
//This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
//Note: messages should be retrieved from a service that will probably store the strings in a resource file
CheckValue(state, LogTest.ErrorMessage, "{OriginalFormat}") &&
//This confirms that an argument with a key of "recordId" was sent with the correct value
//In Application Insights, this will turn up in Custom Dimensions
CheckValue(state, recordId, nameof(recordId))
),
//Confirm the exception type
It.IsAny<NotImplementedException>(),
//Accept any valid Func here. The Func is specified by the extension methods
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
//Make sure the message was logged the correct number of times
Times.Exactly(1)
);
}
private static bool CheckValue(object state, object expectedValue, string key)
{
var keyValuePairList = (IReadOnlyList<KeyValuePair<string, object>>)state;
var actualValue = keyValuePairList.First(kvp => string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0).Value;
return expectedValue.Equals(actualValue);
}
For more context, see this article.
If you need to verify the calls in addition to just provide the instance, it gets somewhat complicated. The reason is that most calls does not actually belong to the ILogger interface itself.
I have written a more detailed answer here.
Here is a small overview.
Example of a method that I have made to work with NSubstitute:
public static class LoggerTestingExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log(
LogLevel.Error,
0,
Arg.Is<FormattedLogValues>(v => v.ToString() == message),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
}
}
And this is how it can be used:
_logger.Received(1).LogError("Something bad happened");
You should try this for mocking ILogger:
mock.Setup(m => m.Log<object>(It.IsAny<LogLevel>(),It.IsAny<EventId>(),It.IsAny<object>(),It.IsAny<Exception>(),It.IsAny<Func<object, Exception,string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, obj, exception, func) =>
{
string msg = func.Invoke(obj, exception);
Console.WriteLine(msg);
});
This worked for me:
private FooController _fooController;
private Mock<ILogger<FooController>> _logger;
[TestInitialize]
public void Setup()
{
_logger = new Mock<ILogger<FooController>>();
_fooController = new FooController(_logger.Object);
}

Manipulating objects with DbSet<T> and IQueryable<T> with NSubstitute returns error

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.

Categories