gpt4 book ai didi

entity-framework - 在 MS 单元测试中使用 DbContext

转载 作者:行者123 更新时间:2023-12-01 12:33:24 25 4
gpt4 key购买 nike

我不确定如何将 EF 融入我的业务逻辑测试中。让我举一个它在运行时如何工作的例子(没有测试,常规应用程序运行):

Context.Set<T>.Add(instance);

当我使用上述通用方法添加实体时,一个实例被添加到上下文中,EF 修复了幕后的所有导航属性。例如,如果存在 [instance.Parent] 属性和 [parent.Instances] 集合属性(一对多关系),EF 会在后台自动将实例添加到 parent.Instances 集合。

我的代码依赖于[parent.Instances]集合,如果它为空就会失败。当我使用 MS 测试框架编写单元测试时,如何重用 EF 的功能,使其仍然可以完成其幕后工作,但将内存用作数据存储,而不是实际的数据库?我对 EF 是否成功地添加、修改或删除了数据库中的某些内容并不感兴趣,我只对在内存集上获得 EF 魔法感兴趣。

最佳答案

我一直在使用我创建的模拟 DbContext 和模拟 DbSet 来执行此操作。它们将测试数据存储在内存中,并允许您执行您可以在 DbSet 上执行的大部分标准操作。

您最初获取 DbContext 的代码必须进行更改,以便它在单元测试下运行时获取 MockDbContext。您可以使用以下代码确定您是否在 MSTest 下运行:

public static bool IsInUnitTest
{
get
{
return AppDomain.CurrentDomain.GetAssemblies()
.Any(assembly =>
assembly.FullName.StartsWith(
"Microsoft.VisualStudio.QualityTools.UnitTestFramework"));
}
}

这是 MockDbContext 的代码:

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication5
{
// ProductionDbContext would be DbContext class
// generated by Entity Framework
public class MockDbContext: ProductionDbContext
{
public MockDbContext()
{
LoadFakeData();
}

// Entities (for which we'll provide MockDbSet implementation
// and test data)
public override DbSet<Account> Accounts { get; set; }
public override DbSet<AccountGenLink> AccountGenLinks { get; set; }
public override DbSet<AccountPermit> AccountPermits { get; set; }
public override DbSet<AcctDocGenLink> AcctDocGenLinks { get; set; }

// DbContext method overrides
private int InternalSaveChanges()
{
// Just return 0 in the mock
return 0;
}

public override int SaveChanges()
{
return InternalSaveChanges();
}

public override Task<int> SaveChangesAsync()
{
return Task.FromResult(InternalSaveChanges());
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
// Just ignore the cancellation token in the mock
return SaveChangesAsync();
}

private void LoadFakeData()
{
// Tables
Accounts = new MockDbSet<Account>(this);
Accounts.AddRange(new List<Account>
{
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "CD",
acctnumber = "1", pending = false, BankOfficer1 = string.Empty,
BankOfficer2 = null, Branch = 0, type = "18", drm_rate_code = "18",
officer_code = string.Empty, open_date = new DateTime(2010, 6, 8),
maturity_date = new DateTime(2010, 11, 8), HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", pending = false, BankOfficer1 = "BCK",
BankOfficer2 = string.Empty, Branch = 0, type = "05", drm_rate_code = "00",
officer_code = "DJT", open_date = new DateTime(1998, 9, 14),
maturity_date = null, HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "LN", acctnumber = "1",
pending = false, BankOfficer1 = "LMP", BankOfficer2 = string.Empty,
Branch = 0, type = "7", drm_rate_code = null, officer_code = string.Empty,
open_date = new DateTime(2001, 10, 24),
maturity_date = new DateTime(2008, 5, 2), HostAcctActive = true,
EffectiveAcctStatus = "A"
}
});

AccountGenLinks = new MockDbSet<AccountGenLink>(this);
AccountGenLinks.AddRange(new List<AccountGenLink>
{
// Add your test data here if needed
});

AccountPermits = new MockDbSet<AccountPermit>(this);
AccountPermits.AddRange(new List<AccountPermit>
{
// Add your test data here if needed
});

AcctDocLinks = new MockDbSet<AcctDocLink>(this);
AcctDocLinks.AddRange(new List<AcctDocLink>
{
new AcctDocLink { ID = 1, SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", DocID = 50, DocType = 5 },
new AcctDocLink { ID = 25, SSN_EIN = "123456789", CODE = "6", accttype = "CD",
acctnumber = "1", DocID = 6750, DocType = 5 }
});
}
}
}

这里是 MockDbSet 的代码:

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

namespace ConsoleApplication5
{
public sealed class MockDbSet<TEntity> : DbSet<TEntity>, IQueryable,
IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity> where TEntity : class
{
public MockDbSet(MockDbContext context)
{
// Get entity set for entity
// Used when we figure out whether to generate
// IDENTITY values
EntitySet = ((IObjectContextAdapter) context).ObjectContext
.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.FirstOrDefault(item => item.Name == typeof(TEntity).Name);

Data = new ObservableCollection<TEntity>();
Query = Data.AsQueryable();
}

private ObservableCollection<TEntity> Data { get; set; }

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

private EntitySetBase EntitySet { get; set; }

Expression IQueryable.Expression
{
get { return Query.Expression; }
}

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

public override ObservableCollection<TEntity> Local
{
get { return Data; }
}

IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<TEntity>(Query.Provider); }
}

private IQueryable Query { get; set; }

public override TEntity Add(TEntity entity)
{
GenerateIdentityColumnValues(entity);
Data.Add(entity);
return entity;
}

public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Add(entity);
return entities;
}

public override TEntity Attach(TEntity entity)
{
return Add(entity);
}

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

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

public override TEntity Find(params object[] keyValues)
{
throw new NotSupportedException();
}

public override Task<TEntity> FindAsync(params object[] keyValues)
{
return FindAsync(CancellationToken.None, keyValues);
}

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

private void GenerateIdentityColumnValues(TEntity entity)
{
// The purpose of this method, which is called when adding a row,
// is to ensure that Identity column values are properly initialized
// before performing the add. If we were making a "real" Entity Framework
// Add() call, this task would be handled by the data provider and the
// value(s) would then be propagated back into the entity. In the case
// of this mock, there is nothing that will do that, so we have to make
// this at-least token effort to ensure the columns are properly initialized.

// In SQL Server, an Identity column can be of one of the following
// data types: tinyint, smallint, int, bigint, decimal (with a scale of 0),
// or numeric (with a scale of 0); This method handles the integer types
// (the others are typically not used).
foreach (var member in EntitySet.ElementType.Members.ToList())
{
if (member.IsStoreGeneratedIdentity)
{
// OK, we've got a live one; do our thing.
//
// Note that we'll get the current value of the column and,
// if it is nonzero, we'll leave it alone. We do this because
// the test data in our mock DbContext provides values for the
// Identity columns and many of those values are foreign keys
// in other entities (where we also provide test data). We don't
// want to disturb any existing relationships defined in the test data.
Type columnDataType = null;
foreach (var metadataProperty in member.TypeUsage.EdmType.MetadataProperties.ToList())
{
if (metadataProperty.Name != "PrimitiveTypeKind")
continue;
switch ((PrimitiveTypeKind)metadataProperty.Value)
{
case PrimitiveTypeKind.SByte:
columnDataType = typeof(SByte);
break;
case PrimitiveTypeKind.Int16:
columnDataType = typeof(Int16);
break;
case PrimitiveTypeKind.Int32:
columnDataType = typeof(Int32);
break;
case PrimitiveTypeKind.Int64:
columnDataType = typeof(Int64);
break;
default:
throw new InvalidOperationException();
}

var identityColumnGetter = entity.GetType().GetProperty(member.Name).GetGetMethod();
var identityColumnSetter = entity.GetType().GetProperty(member.Name).GetSetMethod();

Int64 specifiedColumnValue = 0;
switch (columnDataType.Name)
{
case "SByte":
specifiedColumnValue = (SByte)identityColumnGetter.Invoke(entity, null);
break;
case "Int16":
specifiedColumnValue = (Int16)identityColumnGetter.Invoke(entity, null);
break;
case "Int32":
specifiedColumnValue = (Int32)identityColumnGetter.Invoke(entity, null);
break;
case "Int64":
specifiedColumnValue = (Int64)identityColumnGetter.Invoke(entity, null);
break;
}
if (specifiedColumnValue != 0)
break;

Int64 maxExistingColumnValue = 0;

switch (columnDataType.Name)
{
case "SByte":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (SByte)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (SByte)(++maxExistingColumnValue) });
break;
case "Int16":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int16)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int16)(++maxExistingColumnValue) });
break;
case "Int32":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int32)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int32)(++maxExistingColumnValue) });
break;
case "Int64":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int64)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int64)(++maxExistingColumnValue) });
break;
}
}
}
}
}

IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
{
return new MockDbAsyncEnumerator<TEntity>(Data.GetEnumerator());
}

IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return Data.GetEnumerator();
}

public override TEntity Remove(TEntity entity)
{
Data.Remove(entity);
return entity;
}

public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Remove(entity);
return entities;
}

public override DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
throw new NotSupportedException();
}
}

internal class MockDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
internal MockDbAsyncQueryProvider(IQueryProvider queryProvider)
{
QueryProvider = queryProvider;
}

private IQueryProvider QueryProvider { get; set; }

public IQueryable CreateQuery(Expression expression)
{
return new MockDbAsyncEnumerable<TEntity>(expression);
}

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

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

public TResult Execute<TResult>(Expression expression)
{
return QueryProvider.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));
}
}

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

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

IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<T>(this); }
}

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

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

internal class MockDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
public MockDbAsyncEnumerator(IEnumerator<T> enumerator)
{
Enumerator = enumerator;
}

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

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

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

private IEnumerator<T> Enumerator { get; set; }

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

关于entity-framework - 在 MS 单元测试中使用 DbContext,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31395540/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com