使用Moq单元测试通用工作单元和仓储模式框架

3
我会尽力帮助您翻译。以下是需要翻译的内容:

我感到非常苦恼。我正在学习如何使用通用工作单元和存储库模式框架。我没有问题设置控制器、Unity 和视图……它们都可以使用实时数据工作。我的问题是单元测试这些异步存储库。

我已经在 StackOverflow 上看到了很多帖子,在 MSDN 上看到了很多与使用 Moq 模拟 DataContext相关的文章。

然而,在执行测试时,我似乎遇到了一些障碍,不知道该怎么解决。请耐心等待。

这是我正在测试的控制器:

public class TeamsController : Controller
{
   private readonly IUnitOfWorkAsync _uow;
   private readonly IRepositoryAsync<Team> _repo;

   public TeamsController(IUnitOfWorkAsync uow)
   {
      _uow = uow;
      _repo = _uow.RepositoryAsync<Team>();
   }

   // GET: Teams
   public async Task<ViewResult> Index()
   {
      return View(await _repo.Queryable().ToListAsync());
   }
}

这是单元测试:

[TestMethod]
public async Task Index_AccessIndexPage_MustPass()
{
   // arrange
   var data = new List<Team> 
   { 
      new Team { Id = 1 }
   }.AsQueryable();

   Mock<DbSet<Team>> mockSet = data.GenerateMockDBSet<Team>();
   var mockContext = new Mock<IDataContextAsync>();
   mockContext.As<IDBContext>().Setup(c => c.Teams).Returns(mockSet.Object);

   _uow = new UnitOfWork(mockContext.Object);

   // act
   _controller = new TeamsController(_uow);
   var result = await _controller.Index();
   var model = (List<Team>)((ViewResult)result).Model;

   // assert
   Assert.IsNotNull(model);
   Assert.AreEqual(model.Count, 2);
}

这是我从MSDN获得的实用程序:
public static Mock<DbSet<TEnt>> GenerateMockDBSet<TEnt>(this IQueryable<TEnt> data)
            where TEnt : Entity
{
  var mockSet = new Mock<DbSet<TEnt>>();
  mockSet.As<IDbAsyncEnumerable<TEnt>>()
         .Setup(m => m.GetAsyncEnumerator())
         .Returns(new TestDbAsyncEnumerator<TEnt>(data.GetEnumerator()));

  mockSet.As<IQueryable<TEnt>>()
         .Setup(m => m.Provider)
         .Returns(new TestDbAsyncQueryProvider<TEnt>(data.Provider));

  mockSet.As<IQueryable<TEnt>>().Setup(m => m.Expression).Returns(data.Expression);
  mockSet.As<IQueryable<TEnt>>().Setup(m => m.ElementType).Returns(data.ElementType);
  mockSet.As<IQueryable<TEnt>>().Setup(m => m.Provider).Returns(data.Provider);
  mockSet.As<IQueryable<TEnt>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator);

  return mockSet;
}

这是单元测试中实际的异常:

Test method MyMVC.Tests.Controllers.TeamsControllerTest.Index_AccessIndexPage_MustPass threw exception: 
System.ArgumentNullException: Value cannot be null.
Parameter name: source
Result StackTrace:  
at System.Data.Entity.Utilities.Check.NotNull[T](T value, String parameterName)
   at System.Data.Entity.QueryableExtensions.ToListAsync[TSource](IQueryable`1 source)

这个异常是在实际调用 .Queryable() 方法时触发的,因为 IRepositoryAsync _repo 似乎会抛出一个 null 值。

有人能提供帮助吗?


你能否编辑你的帖子并包含你的UnitOfWork代码?如果我们可以在这里看到它,那会更容易和完整。具体来说,我想看到构造函数和RepositoryAsync调用,因为你正在将mockContext传递给ouw的构造函数。我的猜测是在你的TeamsController ctor中,_uow.RepositoryAsync<Team>()正在调用你尚未设置的mockContext上的某些内容,因此导致TeamsController._repo在类实例化时为空。 - Damon
嗨,Damon,正如我之前提到的那样。UOW和Gen Repo的代码是由一个开源库提供的。它不一定始终为null,而只有在我将其转换为.Queryable()的部分为null。干杯! - Martin Ongtangco
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Rogala
1个回答

0

你的控制器依赖于 IUnitOfWorkAsync,这就是需要被模拟的。你不应该使用一个“真实”的新 UnitOfWork。

我建议你为异步仓库创建模拟:

var myData = new List<Team>(); //fill whatever test data you need

var repo = new Mock<IRepositoryAsync<Team>>();
repo.Setup(r=>r.Queryable()).Returns(myData.AsQueryable());
var uow = new Mock<IUnitOfWorkAsync>();
uow.Setup(u=>u.RepositoryAsync<Team>()).Returns(repo.Object);

太好了,Sunny,我终于搞清楚了!:在Returns中,我使用的不是myData.AsQueryable(),而是应用了这个:repo.Setup(r => r.Queryable()).Returns(mockContext.As<IBugFixRocksContext>().Object.Teams.AsQueryable()); - Martin Ongtangco
@MartinOngtangco:你可以那样做,但在我看来,你深入得太多了。为什么需要使用另一个需要设置的模拟?使用列表更自然,因为你可以更容易地准备它,并根据需要进行各种测试和删除项目。例如,当你决定添加一些Where子句时,模拟会妨碍你。 - Sunny Milenov
我在可查询列表方面遇到了问题。这是唯一的解决方案。谢谢。 - Martin Ongtangco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接