模拟标识列行为

时间:2016-01-07 21:58:22

标签: c# entity-framework unit-testing mocking

我正在嘲笑DbContext进行单元测试,当您在数据库中保存更改时,您添加的实例会拉出数据库标识列分配的新ID,有没有办法模拟这种行为?,I真的不知道从哪里开始。

var acc = new Account {Name = "A New Account"};

_db.Accounts.Add(acc);
_db.SaveChanges();

Assert.IsTrue(acc.Id > 0);

哪里

public class TestDbContext : IEntities
{
    public DbSet<Instance> Accounts { get; set; } = new MockDbSet<Accounts>();
}

并且

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace ControliApiTests.Data
{

public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : class
{
    readonly ObservableCollection<T> _data;
    readonly IQueryable _queryable;

    public MockDbSet()
    {
        _data = new ObservableCollection<T>();
        _queryable = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from MockDbSet<T> and override Find");
    }

    public Task<T> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
    {
        throw new NotImplementedException();
    }

    public override T Add(T item)
    {
        _data.Add(item);
        return item;
    }

    public override IEnumerable<T> AddRange(IEnumerable<T> entities)
    {
        var addRange = entities as T[] ?? entities.ToArray();
        foreach (var entity in addRange)
        {
            _data.Add(entity);
        }
        return addRange;
    }

    public override T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public override T Attach(T item)
    {
        _data.Add(item);
        return item;
    }

    public override T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public override TDerivedEntity Create<TDerivedEntity>()
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public override ObservableCollection<T> Local
    {
        get { return _data; }
    }

    Type IQueryable.ElementType
    {
        get { return _queryable.ElementType; }
    }

    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return _queryable.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return new AsyncQueryProviderWrapper<T>(_queryable.Provider); }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }
}

internal class AsyncQueryProviderWrapper<T> : IDbAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

    internal AsyncQueryProviderWrapper(IQueryProvider inner)
    {
        _inner = inner;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new AsyncEnumerableQuery<T>(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new AsyncEnumerableQuery<TElement>(expression);
    }

    public object Execute(Expression expression)
    {
        return _inner.Execute(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return _inner.Execute<TResult>(expression);
    }

    public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute(expression));
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }
}

public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>
{
    public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable)
    {
    }

    public AsyncEnumerableQuery(Expression expression) : base(expression)
    {
    }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator()
    {
        return new AsyncEnumeratorWrapper<T>(this.AsEnumerable().GetEnumerator());
    }

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
    {
        return GetAsyncEnumerator();
    }
}

public class AsyncEnumeratorWrapper<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public AsyncEnumeratorWrapper(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public void Dispose()
    {
        _inner.Dispose();
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IDbAsyncEnumerator.Current
    {
        get { return Current; }
    }
}
}

2 个答案:

答案 0 :(得分:2)

如果你定义

private static int IdentityCounter = 1;
在模拟实现中

并为每个添加的项增加一个,只要应用域存在,您将获得一个不会重置的递增值。

如果您的测试允许多线程添加,请使用Interlocked.Increment更新计数器。

请注意,您当前的实现不要求对象具有Id属性。如果测试中的所有类都具有此类属性,则可以定义要使用的接口,而不是允许class的任何类。

public interface DbEntity
{
    int Id { get; set; }
}

public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : DbEntity

通过这种更改,您的Add实现可能看起来像

public override T Add(T item)
{
    item.Id = IdentityCounter++; // Or use Interlocked.Increment to support multithreading
    _data.Add(item);
    return item;
}

答案 1 :(得分:-1)

如果您不想使用界面,可以使用反射和扩展方法来获取并评估ID

.*
grep