Entity framework core mock .Include and .Theninclude with nsubstitute

asp.net-core c# entity-framework-core nsubstitute unit-testing

Question

I did create successfull a mock for DbSet for Entity Famework Core 1.1.2 with nsubstitue

class FakeDbSet<TEntity> : DbSet<TEntity>, IQueryable<TEntity>, IAsyncEnumerable<TEntity> where TEntity : class

Its got an internal list that hold the data to mock add, find and remove methods. How to to mock the .Include and .ThenInclude methods to get joins working?

My current FakeDbSet implementation:

/// <summary>
///     FakeDbSet holds entries in interal list to fake add and delete methods
///     Mocking DbSet normaly would only work for getter not for setter
/// </summary>
/// <typeparam name="TEntity"></typeparam>
class FakeDbSet<TEntity> : DbSet<TEntity>, IQueryable<TEntity>, IAsyncEnumerable<TEntity> where TEntity : class
{
    /// <summary>
    /// Static constructor. Determines the which properties are key properties
    /// </summary>
    static FakeDbSet()
    {
        var type = typeof(TEntity);

        foreach (var property in type
            .GetProperties()
            .Where(v => v.GetCustomAttributes(false).OfType<KeyAttribute>().Any()))
        {
            Keys.Add(property);
        }
    }

    /// <summary>
    /// Contains PropertyInfo objects for each of the key properties
    /// </summary>
    private static readonly List<PropertyInfo> Keys = new List<PropertyInfo>();

    /// <summary>
    /// The data we will query against in a List object
    /// </summary>
    private readonly IList<TEntity> _data;

    /// <summary>
    /// The data we will query against in a IQueryable object
    /// </summary>
    private readonly IQueryable<TEntity> _queryable;

    /// <summary>
    /// A dictionary to look up the current status of an object
    /// </summary>
    private readonly Dictionary<TEntity, EntityStatus> _entityStatus =
        new Dictionary<TEntity, EntityStatus>();

    /// <summary>
    /// Observable collection of data
    /// </summary>

    /// <summary>
    /// Constructor.  Expects an IList of entity type
    /// that becomes the data store
    /// </summary>
    /// <param name="data"></param>
    public FakeDbSet(IList<TEntity> data)
    {
        _data = data;
        _entityStatus.Clear();
        foreach (var item in data)
        {
            _entityStatus[item] = EntityStatus.Normal;
        }
        _queryable = data.AsQueryable();

        // The fake provider wraps the real provider (for "List<TEntity")
        // so that it can log activities
        Provider = new FakeAsyncQueryProvider<TEntity>(_queryable.Provider);
    }


    /// <inheritdoc />
    public override EntityEntry<TEntity> Add(TEntity entity)
    {
        _data.Add(entity);
        _entityStatus[entity] = EntityStatus.Added;

        return null;
    }

    /// <inheritdoc />
    public override async Task<EntityEntry<TEntity>> AddAsync(TEntity entity, CancellationToken cancellationToken = new CancellationToken())
    {
        return await Task.FromResult(Add(entity));
    }

    /// <inheritdoc />
    public override Task AddRangeAsync(params TEntity[] entities)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Implements the Find function of IdbSet.
    /// Depends on the keys collection being
    /// set to the key types of this entity
    /// </summary>
    /// <param name="keyValues"></param>
    /// <returns></returns>
    public override TEntity Find(params object[] keyValues)
    {
        if (keyValues.Length != Keys.Count)
        {
            throw new ArgumentException(
                string.Format("Must supply {0} key values", Keys.Count),
                "keyValues"
            );
        }

        var query = _queryable;

        var parameterExpression = Expression.Parameter(typeof(TEntity), "v");

        for (int i = 0; i < Keys.Count; i++)
        {
            var equalsExpression = Expression.Equal(
                // key property
                Expression.Property(parameterExpression, Keys[i]),
                // key value
                Expression.Constant(keyValues[i], Keys[i].PropertyType)
            );

            var whereClause = (Expression<Func<TEntity, bool>>) Expression.Lambda(
                equalsExpression,
                new ParameterExpression[] {parameterExpression}
            );

            query = query.Where(whereClause);
        }

        var result = query.ToList();

        return result.SingleOrDefault();
    }

    public override async Task<TEntity> FindAsync(params object[] keyValues)
    {
        return await new Task<TEntity>(() => Find(keyValues));
    }

    /// <summary>
    /// Implements the Remove function of IDbSet
    /// </summary>
    /// <param name="entity"></param>
    /// <returns></returns>
    public override EntityEntry<TEntity> Remove(TEntity entity)
    {
        _data.Remove(entity);
        _entityStatus[entity] = EntityStatus.Deleted;

        return null;
    }

    public IEnumerator<TEntity> GetEnumerator()
    {
        return _queryable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _queryable.GetEnumerator();
    }

    public Type ElementType => _queryable.ElementType;

    public Expression Expression => _queryable.Expression;

    public IQueryProvider Provider { get; }

    public enum EntityStatus
    {
        None,
        Added,
        Deleted,
        Normal
    }

    /// <inheritdoc />
    IAsyncEnumerator<TEntity> IAsyncEnumerable<TEntity>.GetEnumerator()
    {
        return new FakeAsyncEnumerator<TEntity>(_queryable.GetEnumerator());
    }
}
1
2
8/27/2017 7:31:58 AM

Popular Answer

Update: As I've grown more as a developer, I realized my original answer might head down the wrong path. Nowadays, when I need to test around extension methods (such as ThenInclude() in EFCore), I make sure never to use the syntactic sugar that pretends they are instance methods.

To accomplish this, I create internal fields of the same type as the extension method I wish to use. Then, I set them to reference the static method. Finally, those fields can be made accessible to tests via InternalsVisibleTo and replaced with mocks when needed. This solution I find much simpler and less brittle than my original answer, below.

Original answer

There is a way to do this in .NET Core. I realize your question is a bit old, and I hope you've already found the answer, but just in case, here is the strategy I employ. The general idea is to create your own extension methods of the same name which override the EFCore extension methods and use configurable functionality (via an exposed, static property).

QueryableExtensions.cs - Your overriding extension method(s).

public static class QueryableExtensions
{
    public static IIncluder Includer = null;
    public static IIncludableQueryable<T, TProperty> Include<T, TProperty>(
        this IQueryable<T> source,
        Expression<Func<T, TProperty>> path
    )
        where T : class
    {
        return Includer.Include(source, path);
    }
}

DbIncluder.cs - A wrapper around the default functionality.

using EFCore = Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions;

public class DbIncluder : IIncluder
{
    public IIncludableQueryable<T, TProperty> Include<T, TProperty>(
        IQueryable<T> source,
        Expression<Func<T, TProperty>> path
    )
        where T : class
    {
        return EFCore.Include(source, path);
    }
}

IIncluder.cs

public interface IIncluder
{
    IIncludableQueryable<T, TProperty> Include<T, TProperty>(
        IQueryable<T> source,
        Expression<Func<T, TProperty>> path
    ) where T : class;
}

Then, in your Repository.cs (for example), you make room for substituting a Mock IIncluder:

public class Repository : IRepository
{
    static Repository()
    {
        QueryableExtensions.Includer
            = QueryableExtensions.Includer ?? new DbIncluder();
    }


    // ...
}

As long as you've set QueryableExtensions.Includer = [your mock] prior to using your Repo, it should use the mock. Note that this pattern can be used for any of the other EntityFrameworkCore extension methods, as well.

This is a modified version of a solution I found at:

http://blogs.clariusconsulting.net/kzu/how-to-design-a-unit-testable-domain-model-with-entity-framework-code-first/

1
7/8/2019 6:56:35 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow