【问题标题】:Unit testing: How to test generic repository单元测试:如何测试通用存储库
【发布时间】:2019-01-29 19:55:43
【问题描述】:

我正在尝试测试看起来像这样的通用存储库

public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
    protected readonly DbContext DbContext;
    public GenericRepository(DbContext dbContext)
    {
        DbContext = dbContext;
    }

    public string Create(T item)
    {
        if (string.IsNullOrEmpty(item.Id))
        {
            item.Id = Guid.NewGuid().ToString("N");
        }
        item.CreatedAt = DateTime.UtcNow;
        DbContext.Entry(item).State = EntityState.Added;
        DbContext.SaveChanges();
        return item.Id;
        /*using (var dbContext = new MyDbContext())
        {
            item.Id = Guid.NewGuid().ToString("N");
            item.CreatedAt = DateTime.UtcNow;
            dbContext.Entry(item).State = EntityState.Added;
            dbContext.SaveChanges();
            return item.Id;
        }*/
    }
    public T GetById(string id)
    {
        return GetFirst(x => x.Id == id);
    }      

    public T GetFirst(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] nav)
    {
        return GetFiltered(nav).FirstOrDefault(where);
        /*using (var context = new MyDbContext())
        {
            return GetFiltered(context, nav).FirstOrDefault(where);
        }*/
    }


    private IQueryable<T> GetFiltered(params Expression<Func<T, object>>[] nav)
    {
        IQueryable<T> q = DbContext.Set<T>();
        return nav.Aggregate(q, (current, n) => current.Include(n));
    }

}

基于microft's testing fundamental site我试着写了几个测试用例。

下面是单元测试代码

[TestClass]
public class GenericRepositoryTest
{
    private Foo _foo;
    private IQueryable<Foo> _fooList;
    private Mock<DbSet<Foo>> _mockSet;  

    [TestInitialize]
    public void Setup()
    {
        _foo = new Foo
        {            
            EmailId = "foo@bar.com",
            FirstName = "foo",
            LastName = "bar",               
            ProfileId = 27,
            IsDeleted = false,
        };  

        _fooList = new List<Foo>
        {
            new Foo{EmailId = "one@bar.com", FirstName = "one", LastName = "bar", ProfileId = 28, IsDeleted = false}, 
            new Foo{EmailId = "two@bar.com", FirstName = "two", LastName = "bar", ProfileId = 29, IsDeleted = false},    
        }.AsQueryable();
        _mockSet = new Mock<DbSet<Foo>>();
        _mockSet.As<IQueryable<Foo>>().Setup(m => m.Provider).Returns(_fooList.Provider);
        _mockSet.As<IQueryable<Foo>>().Setup(m => m.Expression).Returns(_fooList.Expression);
        _mockSet.As<IQueryable<Foo>>().Setup(m => m.ElementType).Returns(_fooList.ElementType);
        _mockSet.As<IQueryable<Foo>>().Setup(m=>m.GetEnumerator()).Returns(_fooList.GetEnumerator());
    }

    [TestMethod]
    public void Create_GivenEntity_ReturnsGuidId()
    {
        //Arrange
        var guidId = Guid.NewGuid().ToString("N");
        var dbContext = new Mock<MyDbContext>().Object;

        IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(dbContext).Object;

        //Act
        _waitingQueue.Id = guidId;
        var actualId = genericRepository.Create(_foo);

        //Assert
        Assert.IsNotNull(actualId);
        Assert.AreEqual(actualId, guidId);

    }

    [TestMethod]
    public void GetById_GivenEntityId_ReturnsEntity()
    {
        //Arrange
        var id = Guid.NewGuid().ToString("N");
        _foo.Id = id;

        var mockContext = new Mock<MyDbContext>();
        mockContext.Setup(c => c.Foo).Returns(_mockSet.Object);
        IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(mockContext.Object).Object;

        //Act
        var fooId = genericRepository.Create(_foo);
        var fooObject = genericRepository.GetById(id);

        //Assert
        fooObject.PropertiesShouldEqual(_foo);
    }
}

这里是 DbContext

public class MyDbContext : DbContext
{
    public MyDbContext() : base("fakeConnectionString")
    {
    }

    public virtual DbSet<Foo> WaitingQueues { get; set; }
}

我是单元测试的新手,我不知道我采用的方法是否正确。

目前,第一个测试 Create_GivenEntity_ReturnsGuidId 通过,但第二个测试 GetById_GivenEntityId_ReturnsEntity 失败。

我得到的错误是在 GenericRepository 的 GetFirst(Expression&lt;Func&lt;T, bool&gt;&gt; @where, params Expression&lt;Func&lt;T, object&gt;&gt;[] nav) 方法上

这是因为GetFiltered(params Expression&lt;Func&lt;T, object&gt;&gt;[] nav) 在被GetFirst 调用时返回null。

是因为此时TGenericRepository&lt;T&gt; 是未知的吗?

我得到的错误是

System.ArgumentNullException
HResult=0x80004003
Message=Value cannot be null.
Parameter name: source

当我调试时,我可以看到 GetById(string id) 方法传递了正确的 id

谁能建议我该怎么做?我应该采取什么方法?

【问题讨论】:

  • 顺便说一句,在测试GetById_GivenEntityId_ReturnsEntity中,您使用了从存储库中创建的方法,但它没有在代码中声明,您可以添加它吗?
  • 添加方法创建
  • 说实话.. 你不能对通用存储库进行单元测试。您应该对其进行集成测试(使用单元测试项目) 单元测试 - 测试一个永远不会改变的工作单元。 (1+1=2) 如果它是通用的,则没有要测试的单元。我对 Generic Repositories 进行的集成测试是使用内存数据库,编写用例,然后编写测试(SpecFlow 对此非常有用)——这样你测试的用例总是会得到想要的结果。如果有人更改了一个实现(例如 OrderComplete),那么如果有人在计算中犯了错误,相应的 UseCase 应该会失败。
  • 根据您的 MIcrosoft Link.. 看 GetAllBlogsAsync_orders_by_name 是一个非常简单的集成测试。 (使用模拟集,但它可以是任何东西)

标签: c# unit-testing generics moq mstest


【解决方案1】:

您没有设置 DbContext.Set(),因此,由于 Mock 默认处于松散行为,它应该返回 null。

集合的类型不正确。

// should be Mock<DbSet<Foo>>
private Mock<DbSet<WaitingQueue>> _mockSet;
// what it MyDbContext ? should not it be DbContext ? has it is in 
// GenericRepository
var mockContext = new Mock<MyDbContext>();
// what is the point of this line ?
mockContext.Setup(c => c.WaitingQueues).Returns(_mockSet.Object);
// how to setup
mockContext.Setup(c => c.Set<Foo>()).Returns(_mockSet.Object);

nav 始终为 null,因为您只使用谓词调用 GetFirst。

public T GetById(string id)
{
     // nav is null
     return GetFirst(x => x.Id == id);
}      

另外,如果你想测试 GenericRepository 类,不要在测试中模拟它,否则,对它进行单元测试有什么意义?

您要测试的是背后的逻辑,并测试它是否可以处理所有输入。

例如,GetById方法,当id为null,id为空,id不引用已有实体,测试成功(实体找到)。

【讨论】:

  • 我明白你的意思。谢谢你的建议。正如您提到的,我将代码更改为IGenericRepository&lt;Foo&gt; genericRepository = new GenericRepository&lt;Foo&gt;(mockContext.Object);,但错误仍然存​​在。
  • 你的意思是不需要测试 GetById 方法是否真的通过 id 获取实体,我们应该只测试如果 id 为 null 或为空,它如何处理?
  • 我的意思是你应该测试的主要是不寻常的输入以及你的代码如何处理它,但你还需要测试你的代码如何处理正确的输入。
  • 没错,在这种情况下,我想测试如果给定现有 id,GetById 方法是否可以获取该实体。我希望 CreateMethod 实际创建实体并存储在内存中的某个位置,因为它不写入数据库。至少我希望它在内存对象中
  • 问题是我想将 WaitingQueue 称为 Foo 以使问题更易于理解。我已经设置了 DbSet。请查看更新后的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多