测试实体框架查找方法

Posted

技术标签:

【中文标题】测试实体框架查找方法【英文标题】:Test Entity Framework Find Method 【发布时间】:2013-11-08 00:17:15 【问题描述】:

我正在尝试测试 SystemService.cs 中的 GetSystem(int id) 方法是否返回正确的值,但似乎无法弄清楚如何让所有内容一起发挥作用。似乎无论我做什么,GetSystem() 总是返回 null。这是使用实体框架 6。如果我将 GetSystem 的主体更改为 _context.Systems.SingleOrDefault(s => s.Id = id),那么一切正常,但我真的很想使用 Find()

测试这个的正确方法是什么?我在这个例子中使用 xUnit 和 Moq。 SystemServiceTests.cs 显示我当前使用的代码不起作用。

SystemService.cs

namespace MyProject.Services

  public class SystemService
  
    private readonly MyContext _context;

    public SystemService(MyContext context)
    
      _context = context;
    

    public Models.System GetSystem(int id)
    
      return _context.Systems.Find(id);
    
  

SystemServiceTests.cs

namespace MyProject.Tests.Unit

  public class SystemServiceTests
  
    [Fact]
    public void GetSystemReturnsFromContext()
    
      var data = new List<Models.System> 
        new Models.System  Id = 1, Name = "test 1" ,
        new Models.System  Id = 2, Name = "test 2" 
      .AsQueryable();

      var mockContext = new Mock<MyContext>();

      var mockSet = new Mock<MockableDbSetWithIQueryable<Models.System>>();
      mockContext.Setup(c => c.Systems).Returns(mockSet.Object);

      mockSet.Setup(m => m.Provider).Returns(data.Provider);
      mockSet.Setup(m => m.Expression).Returns(data.Expression);
      mockSet.Setup(m => m.ElementType).Returns(data.ElementType);
      mockSet.Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

      var service = new SystemService(mockContext.Object);
      var system = service.GetSystem(1);

      Assert.NotNull(system); // This is always null
    
  

MyContext.cs

namespace MyProject.Models

  public class MyContext : DbContext
  
    public MyContext()
      : base("DefaultConnection")
    
    

    public virtual DbSet<Models.System> Systems  get; set; 
  

System.cs

namespace MyProject.Models

  public class System
  
    public int Id  get; set; 
    public string Name  get; set; 
  

MockableDbSetWithIQueryable.cs

namespace MyProject.Tests.Helpers

  public abstract class MockableDbSetWithIQueryable<T> : DbSet<T>, IQueryable
    where T : class
  
    public abstract IEnumerator<T> GetEnumerator();
    public abstract Expression Expression  get; 
    public abstract Type ElementType  get; 
    public abstract IQueryProvider Provider  get; 
  

PS。一些代码,特别是MockableDbSetWithIQueryable,可以在http://msdn.microsoft.com/en-US/data/dn314429找到

【问题讨论】:

不能mockContext.Setup(c =&gt; c.Systems).Returns(data);吗? 【参考方案1】:

我能够找到推荐的方法来使用 Entity Framework 6 测试所有内容。此推荐的资源可在 http://msdn.microsoft.com/en-US/data/dn314431 获得。

简而言之,需要为需要测试的每个位创建测试类。我最终做的是以下内容:

TestDbSet.cs

public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>
    where TEntity : class

    ObservableCollection<TEntity> _data;
    IQueryable _query;

    public TestDbSet()
    
        _data = new ObservableCollection<TEntity>();
        _query = _data.AsQueryable();
    

    public override TEntity Add(TEntity item)
    
        _data.Add(item);
        return item;
    

    public override TEntity Remove(TEntity item)
    
        _data.Remove(item);
        return item;
    

    public override TEntity Attach(TEntity item)
    
        _data.Add(item);
        return item;
    

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

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

    public override ObservableCollection<TEntity> Local
    
        get
        
            return _data;
        
    

    Type IQueryable.ElementType
    
        get  return _query.ElementType; 
    

    Expression IQueryable.Expression
    
        get  return _query.Expression; 
    

    IQueryProvider IQueryable.Provider
    
        get  return _query.Provider; 
    

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

    IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
    
        return _data.GetEnumerator();
    

TestSystemDbSet.cs

class TestSystemDbSet : TestDbSet<Models.System>

    public override Models.System Find(params object[] keyValues)
    
        var id = (int)keyValues.Single();
        return this.SingleOrDefault(s => s.Id == id);
    

TestContext.cs

public class TestContext: IContext

    public TestContext()
    
        this.Systems = new TestSystemDbSet();
    

    public DbSet<Models.System> Systems  get; set; 

    public int SaveChangesCount  get; private set; 
    public int SaveChanges()
    
        this.SaveChangesCount++;
        return 1;
    

SystemServiceTests.cs

public class SystemServiceTests

    [Fact]
    public void GetSystemReturnsFromContext()
    
        var context = new TestContext();
        context.Systems.Add(new Models.System  Id = 1, Name = "System 1" );
        context.Systems.Add(new Models.System  Id = 2, Name = "System 2" );
        context.Systems.Add(new Models.System  Id = 3, Name = "System 3" );

        var service = new SystemService(context);
        var system = service.GetSystem(2);

        Assert.NotNull(system);
        Assert.Equal(2, system.Id);
        Assert.Equal("System 2", system.Name);
    

SystemService.cs

public class SystemService : ISystemService

    private readonly IContext _context;

    public SystemService(IContext context)
    
        _context = context;
    

    public Models.System AddSystem(Models.System system)
    
        var s = _context.Systems.Add(system);
        _context.SaveChanges();

        return s;
    

    public Models.System GetSystem(int id)
    
        return _context.Systems.Find(id);
    

ISystemService.cs

public interface ISystemService

    Models.System AddSystem(Models.System system);
    Models.System GetSystem(int id);

【讨论】:

【参考方案2】:

.Find() 正在返回 null,因为这是 System 的默认值。该集合不包含 ID 为 id 的项目。

.Find()List 的一个方法。

我建议你使用LINQ的FirstOrDefault()

原因是,您可以通过返回 IQueryable 来使用延迟加载

【讨论】:

谢谢,我会试试的,但 Find() 不会先查询本地上下文,只有在找不到任何内容时才访问数据库? 看起来无法直接实例化 DbSet,因为它的构造函数被标记为受保护。 好的,我已经更新了我的答案。 Find() 只是查询内存中的列表。我推荐使用FirstOrDefault() 我可以使用FirstOrDefalut(),但在这种情况下我真的很想使用Find(),因为它通过在不必查询数据库时引入了效率。有什么方法可以测试Find()什么时候使用? 问题不在于LINQ 与其他东西,它正在测试框架(实体框架)以确保其正常工作。在这种情况下,Find() 实际上是 DbSet 的成员,它实现了IDbSet&lt;TEntity&gt;。我正在寻找一种模拟 DbSet 的方法,使 Find() 可以像在生产系统中一样工作。但是,我不确定这是否可能。

以上是关于测试实体框架查找方法的主要内容,如果未能解决你的问题,请参考以下文章

使用 moq 对实体框架进行单元测试

Java SE环境中测试JPA实体的简单方法

如何使用实体框架测试视图?

我应该如何设置我的集成测试以使用带有实体框架的测试数据库?

实体框架无法删除数据库,数据库正在使用中

带有内存测试双倍的模拟实体框架