使用 Moq 对通用工作单元和存储库模式框架进行单元测试
Unit Testing Generic Unit of Work and Repository Pattern framework using Moq
我已经无计可施了。我正在学习如何使用 the Generic Unit of Work and Repository pattern framework 。我在设置控制器、统一体和视图时没有问题……它们都在实时数据上工作。我的问题是对这些异步存储库进行单元测试。
我在 Whosebug 和 MSDN 中看到了很多关于 mocking the DataContext using Moq 的文章。
但是,在执行测试时,我似乎遇到了障碍,我不知道如何解决这个问题。请多多包涵。
这是我正在测试的控制器:
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 似乎抛出空值。
有人能帮忙吗?
您的控制器依赖于 IUnitOfWorkAsync,这就是需要模拟的内容。您不应该使用 "real" 新的 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);
我已经无计可施了。我正在学习如何使用 the Generic Unit of Work and Repository pattern framework 。我在设置控制器、统一体和视图时没有问题……它们都在实时数据上工作。我的问题是对这些异步存储库进行单元测试。
我在 Whosebug 和 MSDN 中看到了很多关于 mocking the DataContext using Moq 的文章。
但是,在执行测试时,我似乎遇到了障碍,我不知道如何解决这个问题。请多多包涵。
这是我正在测试的控制器:
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 似乎抛出空值。
有人能帮忙吗?
您的控制器依赖于 IUnitOfWorkAsync,这就是需要模拟的内容。您不应该使用 "real" 新的 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);