after some discussion with AutoMapper team, they recommended me to put my question here.
In short, if I map the expressions that I need using
MapExpression<T>(source)
it works perfectly fine (considering using the AutoMapper.Extensions.ExpressionMapping framework).
BTW, the authors said me that, even if I try to map using
Map(object, sourceType, targetType)
it should work normally but, when I use this method, I have the error as described in the title of this post.
To help, I wrote a full example on how to reproduce the problem as it follows bellow:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
using AutoMapper.Extensions.ExpressionMapping;
namespace AutoMapperExpressionMappingTest
{
public class PresentationModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class ApplicationModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class DomainModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class PresentationPerson
{
private readonly IMapper _mapper;
public PresentationPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<PresentationModelPerson> List(int take, int skip,
Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>> orderBy,
Expression<Func<PresentationModelPerson, bool>> where, IList<Expression<Func<PresentationModelPerson, object>>> includes)
{
var applicationTake = take;
var applicationSkip = skip;
/*
* if I map this way the mapping on domain class will fail with the following error:
* System.InvalidOperationException: 'Code supposed to be unreachable'
* (there's a reason on my project to use this way)
*/
dynamic applicationOrderByObject = _mapper.Map(orderBy,
typeof(Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>>
),
typeof(Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>
>));
/*
// if I map this way, it works perfectly //
var applicationOrderBy =
_mapper
.MapExpression<
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>>
>(orderBy);
*/
var applicationWhere = _mapper.MapExpression<Expression<Func<ApplicationModelPerson, bool>>>(where);
var applicationInclude =
_mapper.MapExpressionList<Expression<Func<ApplicationModelPerson, object>>>(includes).ToList();
var applicationPerson = new ApplicationPerson(_mapper);
applicationPerson.List(applicationTake, applicationSkip, applicationOrderByObject, applicationWhere,
applicationInclude);
throw new NotImplementedException();
}
}
public class ApplicationPerson
{
private readonly IMapper _mapper;
public ApplicationPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<ApplicationModelPerson> List(int take, int skip,
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>> orderBy,
Expression<Func<ApplicationModelPerson, bool>> where, IList<Expression<Func<ApplicationModelPerson, object>>> includes)
{
var domainTake = take;
var domainSkip = skip;
// this mapping will fail whatever I use this way or _mapper.Map(object, sourceType, targetType) //
var domainOrderBy =
_mapper
.MapExpression<
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>>
>(orderBy);
var domainWhere = _mapper.MapExpression<Expression<Func<DomainModelPerson, bool>>>(where);
var domainInclude =
_mapper.MapExpressionList<Expression<Func<DomainModelPerson, object>>>(includes).ToList();
var domainPerson = new DomainPerson(_mapper);
domainPerson.List(domainTake, domainSkip, domainOrderBy, domainWhere,
domainInclude);
throw new NotImplementedException();
}
}
public class DomainPerson
{
private readonly IMapper _mapper;
public DomainPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<DomainModelPerson> List(int take, int skip,
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>> orderBy,
Expression<Func<DomainModelPerson, bool>> where, IList<Expression<Func<DomainModelPerson, object>>> includes)
{
throw new NotImplementedException();
}
}
public class ModelProfile : Profile
{
public ModelProfile()
{
CreateMap<PresentationModelPerson, ApplicationModelPerson>().ReverseMap();
CreateMap<ApplicationModelPerson, DomainModelPerson>().ReverseMap();
}
}
public class ExpressionProfile : Profile
{
public ExpressionProfile()
{
CreateMap<Expression<Func<PresentationModelPerson, bool>>,
Expression<Func<ApplicationModelPerson, bool>>>().ReverseMap();
CreateMap<Expression<Func<IQueryable<PresentationModelPerson>,
IOrderedQueryable<PresentationModelPerson>>>,
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>>>().ReverseMap();
CreateMap<IList<Expression<Func<PresentationModelPerson, object>>>,
IList<Expression<Func<ApplicationModelPerson, object>>>>().ReverseMap();
CreateMap<Expression<Func<ApplicationModelPerson, bool>>,
Expression<Func<DomainModelPerson, bool>>>().ReverseMap();
CreateMap<Expression<Func<IQueryable<ApplicationModelPerson>,
IOrderedQueryable<ApplicationModelPerson>>>,
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>>>().ReverseMap();
CreateMap<IList<Expression<Func<ApplicationModelPerson, object>>>,
IList<Expression<Func<DomainModelPerson, object>>>>().ReverseMap();
}
}
public class Container
{
public IMapper Mapper { get; }
public Container()
{
var mapperConfiguration = new MapperConfiguration(
configuration =>
{
configuration.AddExpressionMapping();
configuration.AddProfile<ModelProfile>();
configuration.AddProfile<ExpressionProfile>();
configuration.AllowNullCollections = true;
});
Mapper = mapperConfiguration.CreateMapper();
Mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
}
internal class Program
{
private static void Main(string[] args)
{
var mapper = new Container().Mapper;
var presentationPerson = new PresentationPerson(mapper);
Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>> orderBy = persons =>
persons.OrderByDescending(person => person.Birthday);
Expression<Func<PresentationModelPerson, bool>> where = person => !string.IsNullOrEmpty(person.Name);
presentationPerson.List(1, 100, orderBy, where,
new List<Expression<Func<PresentationModelPerson, object>>>());
}
}
}
Is there anything that I'm missing here? In time: .NET Core 2.2, AutoMapper 9.0, AutoMapper.Extensions.ExpressionMapping 3.0.1 and nothing else.
Thank you.
Author answered and confirmed that it's a bug so, I'm posting the workaround they gave me on their Github page.
Here's the link for the issue: https://github.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/issues/40
We need to create a helper for it, like this:
public static class ExpressionMappingHelper
{
public static LambdaExpression MapExpression(this IMapper mapper, LambdaExpression expression, Type sourceExpressionType, Type destExpressionType)
{
if (expression == null)
return default;
//This calls public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
//in AutoMapper.Extensions.ExpressionMapping.MapperExtensions
return (LambdaExpression)"MapExpression".GetMapExpressionMethod().MakeGenericMethod
(
sourceExpressionType,
destExpressionType
).Invoke(null, new object[] { mapper, expression });
}
private static MethodInfo GetMapExpressionMethod(this string methodName)
=> typeof(AutoMapper.Extensions.ExpressionMapping.MapperExtensions).GetMethods().Single(m => m.Name == methodName && m.GetGenericArguments().Length == 2);
}
Then call the extension method like this:
dynamic applicationOrderByObject = _mapper.MapExpression(orderBy,
typeof(Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>>
),
typeof(Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>
>));
This will be the workaround until the AutoMapper team fixes it.
Hope that this can help someone as it helped me.
Related
I am trying to write some tests for an existing service we have. It uses the DbContext (In our case, named DatabaseContext) and the constructor looks like this:
public GenericOrderProvider(DatabaseContext context, IOrderHandler<T> orderHandler)
{
_orderHandler = orderHandler;
_context = context;
_dbSet = context.Set<T>();
}
As you can see, it's generic and sets the _dbSet when it's initialized.
I have this very simple method:
public Attempt<IQueryable<T>> List(params string[] includes)
{
var x = _dbSet.ToList();
return Attempt<IQueryable<T>>.Succeed(_dbSet.OrderBy(m => m.Order));
}
And I wrote this test:
[TestFixture]
public class ListShould
{
[Test]
public void ReturnList()
{
// Assemble
var services = GenericOrderProviderContext.GivenServices();
var provider = services.WhenCreateOrderProvider();
services.DatabaseContext.Set<Attribute>().ReturnsForAnyArgs(new List<Attribute>().ToDbSet());
//services.DatabaseContext.Attributes = new List<Attribute>().ToDbSet();
// Act
var result = provider.List();
// Assert
result.Failure.Should().BeFalse();
result.Result.Count().Should().Be(0);
}
}
When I run that test, I get the error:
NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException : Could not find a call to return from.
The trace specifically targets the line services.DatabaseContext.Set<Attribute>().ReturnsForAnyArgs(new List<Attribute>().ToDbSet()); but I have no idea how to fix it.
As far as I can tell, I am mapping to the right method.
For completeness, here is my test Contexts:
public class GenericOrderProviderContext: DatabaseContextContext<GenericOrderProviderContext>
{
public static GenericOrderProviderContext GivenServices() => new GenericOrderProviderContext();
public IGenericOrderProvider<Attribute> WhenCreateOrderProvider() =>
new GenericOrderProvider<Attribute>(DatabaseContext,
new OrderHandler<Attribute>(DatabaseContext));
public bool IsSequential(List<Attribute> models)
{
return !models.OrderBy(m => m.Order).Select(m => m.Order).Select((i, j) => i - j).Distinct().Skip(1).Any();
}
}
public class DatabaseContextContext<T> where T: DatabaseContextContext<T>
{
public DatabaseContext DatabaseContext;
protected DatabaseContextContext()
{
DatabaseContext = Substitute.For<DatabaseContext>();
}
}
Does anyone know what I can do to resolve this issue?
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
Using the CQS pattern I have a query like this:
public class MyQuery : IQuery<View<SingleView>>
{
public string Token { get; set; }
public Criteria Criteria { get; set; }
}
public class MyQueryHandler : IQueryHandler<MyQuery, View<SingleView>>
{
private readonly IProductRepository _productRepository;
public MyQueryHandler(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public View<SingleView> Handle(MyQuery query)
{
var data = // get my data
return data;
}
}
Off course I have many queries and their belonging QueryHandlers. Now I have a situation that I want to decorate only the MyQueryHandler. I don't want to decorate the rest of the queries.
I am using Autofac, but I just can't get it to work.
Here is how the queries are registered:
builder.RegisterAssemblyTypes(assemblies)
.As(type => type.GetInterfaces()
.Where(interfaceType => interfaceType.IsClosedTypeOf(typeof(IQueryHandler<,>)))
.Select(interfaceType => new KeyedService("queryHandler", interfaceType)));
// Register query decorators
builder.RegisterGenericDecorator(
typeof(LogQueryDecorator<,>),
typeof(IQueryHandler<,>),
"queryHandler");
And here is the decorator I want to use for MyQueryHandler:
public class SaveMyQueryData : IQueryHandler<MyQuery, View<SingleView>>
{
private readonly IQueryHandler<MyQuery, View<SingleView>> _queryHandler;
private readonly IProductRepository _productRepository;
public SaveMyQueryData(
IQueryHandler<MyQuery, View<SingleView>> queryHandler,
IProductRepository productRepository)
{
_queryHandler = queryHandler;
_productRepository = productRepository;
}
public View<SingleView> Handle(MyQuery query)
{
var result = _queryHandler.Handle(query);
// do something with result
return result;
}
}
So how can I register SaveMyQueryData as a decorator just for MyQueryHandler using Autofac?
I finally have it working the way I want. Here is my solution:
Make the decorator open instead of a closed type:
public class SaveMyQueryData : IQueryHandler<Q, R>
{
private readonly IQueryHandler<Q, R> _queryHandler;
private readonly IProductRepository _productRepository;
public SaveMyQueryData(
IQueryHandler<Q, R> queryHandler,
IProductRepository productRepository)
{
_queryHandler = queryHandler;
_productRepository = productRepository;
}
public Q Handle(Q query)
{
var result = _queryHandler.Handle(query);
var view = result as View<SingleView>;
// do something with view
return result;
}
}
Then when registering the type using Autofac, do this:
builder.RegisterGenericDecorator(
typeof(SaveMyQueryData<,>),
typeof(IQueryHandler<,>),
context => context.ImplementationType == typeof(MyQueryHandler));
The last line contains the magic. With the third parameter of RegisterGenericDecorator you can specify a condition when to apply the decorator. This is new since Autofac 4.9 and this solved my problem.
I have some codes like below, I want to write unit tests my method. But I'm stuck in async methods. Can you help me please ?
public class Panel
{
public int Id { get; set; }
[Required] public double Latitude { get; set; }
public double Longitude { get; set; }
[Required] public string Serial { get; set; }
public string Brand { get; set; }
}
public class CrossSolarDbContext : DbContext
{
public CrossSolarDbContext()
{
}
public CrossSolarDbContext(DbContextOptions<CrossSolarDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public interface IGenericRepository<T>
{
Task<T> GetAsync(string id);
IQueryable<T> Query();
Task InsertAsync(T entity);
Task UpdateAsync(T entity);
}
public abstract class GenericRepository<T> : IGenericRepository<T>
where T : class, new()
{
protected CrossSolarDbContext _dbContext { get; set; }
public async Task<T> GetAsync(string id)
{
return await _dbContext.FindAsync<T>(id);
}
public IQueryable<T> Query()
{
return _dbContext.Set<T>().AsQueryable();
}
public async Task InsertAsync(T entity)
{
_dbContext.Set<T>().Add(entity);
await _dbContext.SaveChangesAsync();
}
public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
}
}
public interface IPanelRepository : IGenericRepository<Panel> { }
public class PanelRepository : GenericRepository<Panel>, IPanelRepository
{
public PanelRepository(CrossSolarDbContext dbContext)
{
_dbContext = dbContext;
}
}
[Route("[controller]")]
public class PanelController : Controller
{
private readonly IPanelRepository _panelRepository;
public PanelController(IPanelRepository panelRepository)
{
_panelRepository = panelRepository;
}
// GET panel/XXXX1111YYYY2222
[HttpGet("{panelId}")]
public async Task<IActionResult> Get([FromRoute] string panelId)
{
Panel panel = await _panelRepository.Query().FirstOrDefaultAsync(x => x.Serial.Equals(panelId, StringComparison.CurrentCultureIgnoreCase));
if (panel == null) return NotFound();
return Ok(panel);
}
}
public class PanelControllerTests
{
private readonly PanelController _panelController;
private static readonly Panel panel = new Panel { Id = 1, Brand = "Areva", Latitude = 12.345678, Longitude = 98.7655432, Serial = "AAAA1111BBBB2222" };
private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();
private readonly Mock<IPanelRepository> _panelRepositoryMock = new Mock<IPanelRepository>();
public PanelControllerTests()
{
_panelRepositoryMock.Setup(x => x.Query()).Returns(panels);
// I also tried this. I got another error 'Invalid setup on an extension method: x => x.FirstOrDefaultAsync<Panel>(It.IsAny<Expression<Func<Panel, Boolean>>>(), CancellationToken)'
// _panelRepositoryMock.As<IQueryable<Panel>>().Setup(x => x.FirstOrDefaultAsync(It.IsAny<Expression<Func<Panel, bool>>>(), default(CancellationToken))).ReturnsAsync(panel);
_panelController = new PanelController(_panelRepositoryMock.Object);
}
[Fact]
public async Task Register_ShouldInsertOneHourElectricity()
{
IActionResult result = await _panelController.Get("AAAA1111BBBB2222");
Assert.NotNull(result);
var createdResult = result as CreatedResult;
Assert.NotNull(createdResult);
Assert.Equal(201, createdResult.StatusCode);
}
}
I'm getting this error
The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.
I think that I need to mock 'FirstOrDefaultAsync' but I'm not sure and I don't know how to do. I tried something, however it couldn't be compiled.
I get stuck on this issue today and this lib resolve it for me https://github.com/romantitov/MockQueryable completely, please refer:
Mocking Entity Framework Core operations such ToListAsync, FirstOrDefaultAsync etc.
//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
...
};
//2 - build mock by extension
var mock = users.AsQueryable().BuildMock();
//3 - setup the mock as Queryable for Moq
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);
//3 - setup the mock as Queryable for NSubstitute
_userRepository.GetQueryable().Returns(mock);
You could implement an AsyncEnumerable which can be used like this:
private readonly IQueryable<Panel> panels = new AsyncEnumerable(new List<Panel>()
{
panel
});
Here is the implementation of it:
public class AsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
public AsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }
public AsyncEnumerable(Expression expression) : base(expression) { }
public IAsyncEnumerator<T> GetEnumerator()
{
return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this);
}
The AsyncEnumerator class:
public class AsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public AsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public T Current => _inner.Current;
public Task<bool> MoveNext(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
}
The AsyncQueryProvider class:
public class AsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal AsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new AsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return new AsyncEnumerable<TResult>(expression);
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
This is because of your mocking approach; your mock provider just returns panels for Query, and panels is a simple object with LINQ-to-Objects exposing it as queryable:
private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();
Indeed, this does not implement IAsyncQueryProvider. If you can get hold of the regular query provider, you should be able to wrap that with a fake always-synchronous version to spoof it (just use return Task.FromResult(Execute(expression))), but frankly I'm not sure that this would be a useful test... at that point you're skipping so many of the important realities of async that it probably isn't worth it.
For issue in entity framework core ,use Moq.EntityFrameworkCore library and setup your dbset as below:
contextMock.Setup(x => x.entity).ReturnsDbSet(entityList);
I was facing the same issue and solved it that way:
Instead of having my method public async Task I switched it to public void and then resolved the asynchronous methods with .GetAwaiter() instead of await.
When querying for panel, removing the async in firstordefault.
Also remove the async from tolist when querying for analytics
I want to mock a unit of work interface with repositories inside for unit test purpose. So far I am able to do it like below.
namespace Liquid.Service.UnitTest
{
using Liquid.DataAccess.Interface;
using Liquid.Domain;
using Domain.Interface;
using Moq;
using System.Collections.Generic;
using System.Linq;
internal class Helper
{
internal Mock<IUnitOfWork> MockUnitOfWork(ICollection<Dummy> dummies = null,
ICollection<ProductType> productTypes = null)
{
dummies = dummies ?? new List<Dummy>();
productTypes = productTypes ?? new List<ProductType>();
var dummyRepositoryMock = MockDummyRepository(dummies);
var productTypeRepositoryMock = MockProductTypeRepository(productTypes);
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(x => x.DummyRepository)
.Returns(dummyRepositoryMock.Object);
unitOfWorkMock.Setup(x => x.ProductTypeRepository)
.Returns(productTypeRepositoryMock.Object);
return unitOfWorkMock;
}
private Mock<IDummyRepository> MockDummyRepository(ICollection<Dummy> dummies)
{
var dummyRepositoryMock = new Mock<IDummyRepository>();
dummyRepositoryMock.Setup(x => x.FindById(It.IsAny<int>()))
.Returns((int arg1) => dummies.Where(x => x.Id == arg1).SingleOrDefault());
dummyRepositoryMock.Setup(x => x.Add(It.IsAny<Dummy>()))
.Callback((Dummy arg1) => dummies.Add(arg1));
return dummyRepositoryMock;
}
private Mock<IProductTypeRepository> MockProductTypeRepository(ICollection<ProductType> productTypes)
{
var productTypeRepositoryMock = new Mock<IProductTypeRepository>();
productTypeRepositoryMock.Setup(x => x.FindById(It.IsAny<int>()))
.Returns((int arg1) => productTypes.SingleOrDefault(x => x.Id == arg1));
productTypeRepositoryMock.Setup(x => x.Add(It.IsAny<ProductType>()))
.Callback((ProductType arg1) => productTypes.Add(arg1));
return productTypeRepositoryMock;
}
}
}
You see that I've created two method to mock DummyRepository and ProductTypeRepository but because it has same implementation, I think it is redundant for every repositories I have.
Below is the Repositories and IRepository code.
namespace Liquid.DataAccess.Interface
{
using Liquid.Domain;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public interface IDummyRepository : IRepository<Dummy>
{
}
public interface IProductTypeRepository : IRepository<ProductType>
{
}
public interface IRepository<TEntity> where TEntity : class
{
IList<TEntity> GetAll();
Task<List<TEntity>> GetAllAsync();
Task<List<TEntity>> GetAllAsync(CancellationToken cancellationToken);
IList<TEntity> PageAll(int skip, int take);
Task<List<TEntity>> PageAllAsync(int skip, int take);
Task<List<TEntity>> PageAllAsync(CancellationToken cancellationToken, int skip, int take);
TEntity FindById(object id);
Task<TEntity> FindByIdAsync(object id);
Task<TEntity> FindByIdAsync(CancellationToken cancellationToken, object id);
void Add(TEntity entity);
void Update(TEntity entity);
void Remove(TEntity entity);
}
}
How can I use a same method to mock every repositories implementation which inherits IRepository?
UPDATE :
The test is just a simple add and check like below.
[Test]
public void ProductTypeService_Add()
{
// GIVEN
var productTypeData = new ProductType()
{
Id = 1,
Description = "ProductType1"
};
// WHEN
var unitOfWorkMock = new Helper().MockUnitOfWork();
var productTypeService = new ProductTypeService(unitOfWorkMock.Object);
productTypeService.Add(productTypeData);
unitOfWorkMock.Verify(x => x.SaveChanges());
// THEN
Assert.That(productTypeService.FindById(1) != null);
Assert.That(productTypeService.FindById(2) == null);
// WHEN
var productTypeData2 = new ProductType()
{
Id = 2,
Description = "ProductType2"
};
productTypeService.Add(productTypeData2);
// THEN
Assert.That(productTypeService.FindById(2) != null);
}
IMHO you are testing the wrong thing; namely you are testing that an in-memory collection (a List<T>) can store data and the data can be found in the collection. This always yields true because that is the purpose of in-memory collections.
Instead of doing this you either need to create integration tests which will use the actual implementation of the underlying repository (such as Entity Framework) or just test the behavior of your service like this:
[Test]
public void ProductTypeService_WhenAddingNewItem_CallsSaveChanges()
{
var unitOfWork = new Mock<IUnitOfWork>();
// setup the properties of the mock here...
var service = new ProductTypeService(unitOfWork);
service.Add(new ProductType { Id = 2, Description = "some product" });
unitOfWork.AssertWasCalled(_ => _.SaveChanges());
}
This way, you test that your service calls SaveChanges() method; actually saving the data is the responsibility of the repository and as I said above, testing that a list can store data in memory is useless.
I think you have over complicated your question and thus your solution. You don't need interfaces for your various repositories like IDummyRepository and IProductRepository if you are not adding any value to it.
Your data classes
public class Dummy
{
public int Id { get; set; }
}
public class ProductType
{
public int Id { get; set; }
}
Your ProductTypeService (I can only assume this)
public class ProductTypeService
{
private readonly IUnitOfWork _unitOfWork;
public ProductTypeService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void AddProductType(ProductType productType)
{
_unitOfWork.ProductTypes.Add(productType);
}
}
Your IUnitOfWork
public interface IUnitOfWork
{
IRepository<Dummy> Dummies { get; set; }
IRepository<ProductType> ProductTypes { get; set; }
}
Your IRepository remains unchanged so I won't copy paste it here!
Finally your unit test
[TestFixture]
public class Class1
{
private Mock<IUnitOfWork> _unitOfWorkMock;
private Mock<IRepository<Dummy>> _dummyRepositoryMock;
private Mock<IRepository<ProductType>> _productTypeRepositoryMock;
[SetUp]
public void Setup()
{
_unitOfWorkMock = new Mock<IUnitOfWork>();
_dummyRepositoryMock = CreateMock<Dummy>();
_productTypeRepositoryMock = CreateMock<ProductType>();
_unitOfWorkMock.Setup(u => u.Dummies).Returns(_dummyRepositoryMock.Object);
_unitOfWorkMock.Setup(u => u.ProductTypes).Returns(_productTypeRepositoryMock.Object);
}
[Test]
public void product_type_service_should_add_item_to_the_underlying_repository()
{
var productTypeService = new ProductTypeService(_unitOfWorkMock.Object);
var productType = new ProductType {Id = 10};
productTypeService.AddProductType(productType);
_productTypeRepositoryMock.Verify(r => r.Add(It.Is<ProductType>(p => p.Id == productType.Id)), Times.Once());
}
private Mock<IRepository<T>> CreateMock<T>() where T : class
{
var mock = new Mock<IRepository<T>>();
// do more common magic here
return mock;
}
}