【发布时间】:2014-06-10 17:18:28
【问题描述】:
我正在对我的 BusAcnts 控制器进行单元测试来改进生产代码。该视图包含一个 WebGrid,我正在使用 Stuart Leeks WebGrid 服务代码 (_busAcntService.GetBusAcnts) 来处理分页和排序。
单元测试失败,出现“System.NullReferenceExceptionObject 引用未设置为对象实例”。错误。如果我在调试中运行测试并在控制器中调用服务的点放置一个断点,并在被调用方法(GetBusAcnts)上的服务中放置另一个断点,并尝试单步执行测试失败(具有相同的 NullReference 错误)在调用服务的点。我无法进入服务以查看问题的根源。
出于测试目的,我将基本查询从服务中提取出来,并将其放入控制器中的 GetBusAcnts 方法中,以模拟服务的大部分功能。当我在控制器中调用 GetBusAcnts 方法时,而不是在服务中调用测试通过的方法。
这是一个使用 xUnit 1.9.2、Moq 4.2 的 MVC5 EF6 应用程序。 EF6 模拟数据库设置如本文Testing with a mocking framework (EF6 onwards)。对于这篇文章,我已经简化了代码,我可以并且没有包含正在工作且不需要显示的内容。
我对为什么在调用服务时测试失败感到困惑,并且由于无法单步执行代码,我不知道如何进一步排除故障。
服务接口:
public interface IBusAcntService
{
IEnumerable<BusIdxVm> GetBusAcnts(MyDb dbCtx, out int totalRecords,
int pageSize = -1, int pageIndex = -1, string sort = "Name",
SortDirection sortOrder = SortDirection.Ascending);
}
服务:
public class BusAcntService : IBusAcntService
{
// helpers that take an IQueryable<TAFIdxVM> and a bool to indicate ascending/descending
// and apply that ordering to the IQueryable and return the result
private readonly IDictionary<string, Func<IQueryable<BusIdxVm>, bool,
IOrderedQueryable<BusIdxVm>>>
_busAcntOrderings = new Dictionary<string, Func<IQueryable<BusIdxVm>, bool,
IOrderedQueryable<BusIdxVm>>>
{
{"AcntNumber", CreateOrderingFunc<BusIdxVm, int>(p=>p.AcntNumber)},
{"CmpnyName", CreateOrderingFunc<BusIdxVm, string>(p=>p.CmpnyName)},
{"Status", CreateOrderingFunc<BusIdxVm, string>(p=>p.Status)},
{"Renewal", CreateOrderingFunc<BusIdxVm, int>(p=>p.Renewal)},
{"Structure", CreateOrderingFunc<BusIdxVm, string>(p=>p.Structure)},
{"Lock", CreateOrderingFunc<BusIdxVm, double>(p=>p.Lock)},
{"Created", CreateOrderingFunc<BusIdxVm, DateTime>(t => t.Created)},
{"Modified", CreateOrderingFunc<BusIdxVm, DateTime>(t => t.Modified)}
};
/// <summary>
/// returns a Func that takes an IQueryable and a bool, and sorts the IQueryable
/// (ascending or descending based on the bool).
/// The sort is performed on the property identified by the key selector.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="keySelector"></param>
/// <returns></returns>
private static Func<IQueryable<T>, bool, IOrderedQueryable<T>> CreateOrderingFunc<T,
TKey>(Expression<Func<T, TKey>> keySelector)
{
return (source, ascending) => ascending ? source.OrderBy(keySelector) :
source.OrderByDescending(keySelector);
}
public IEnumerable<BusIdxVm> GetBusAcnts(MyDb dbCtx, out int totalRecords,
int pageSize = -1, int pageIndex = -1, string sort = "Name",
SortDirection sortOrder = SortDirection.Ascending)
{
using (var db = dbCtx) { IQueryable<BusIdxVm> ba;
ba = from bsa in db.BusAcnts select new BusIdxVm { Id = bsa.Id,
AcntNumber = bsa.AcntNumber, CmpnyName = bsa.CmpnyName, Status = bsa.Status,
Renewal = bsa.RnwlStat, Structure = bsa.Structure, Lock = bsa.Lock,
Created = bsa.Created,Modified = bsa.Modified };
totalRecords = ba.Count();
var applyOrdering = _busAcntOrderings[sort]; // apply sorting
ba = applyOrdering(ba, sortOrder == SortDirection.Ascending);
if (pageSize > 0 && pageIndex >= 0) // apply paging
{
ba = ba.Skip(pageIndex * pageSize).Take(pageSize);
}
return ba.ToList(); }
}
}
控制器:
public class BusAcntController : Controller
{
private readonly MyDb _db;
private readonly IBusAcntService _busAcntService;
public BusAcntController() : this(new BusAcntService())
{ _db = new MyDb(); }
public BusAcntController(IBusAcntService busAcntService)
{ _busAcntService = busAcntService; }
public BusAcntController(MyDb db) { _db = db; }
public ActionResult Index(int page = 1, string sort = "AcntNumber",
string sortDir = "Ascending")
{
int pageSize = 15;
int totalRecords;
var busAcnts = _busAcntService.GetBusAcnts( _db, out totalRecords,
pageSize: pageSize, pageIndex: page - 1, sort: sort,
sortOrder: Mth.GetSortDirection(sortDir));
//var busAcnts = GetBusAcnts(_db); //Controller method
var busIdxVms = busAcnts as IList<BusIdxVm> ?? busAcnts.ToList();
var model = new PagedBusIdxModel { PageSize = pageSize, PageNumber = page,
BusAcnts = busIdxVms, TotalRows = totalRecords };
ViewBag._Status = Mth.DrpDwn(DropDowns.Status, ""); ViewBag._Lock = Mth.DrpDwn
return View(model);
}
private IEnumerable<BusIdxVm> GetBusAcnts(MyDb db)
{
IQueryable<BusIdxVm> ba = from bsa in db.BusAcnts select new BusIdxVm
{
Id = bsa.Id, AcntNumber = bsa.AcntNumber, CmpnyName = bsa.CmpnyName,
Status = bsa.Status, Renewal = bsa.RnwlStat, Structure = bsa.Structure,
Lock = bsa.Lock, Created = bsa.Created, Modified = bsa.Modified
};
return ba.ToList();
}
}
单元测试:
[Fact]
public void GetAllBusAcnt()
{
var mockMyDb = MockDBSetup.MockMyDb();
var controller = new BusAcntController(mockMyDb.Object);
var controllerContextMock = new Mock<ControllerContext>();
controllerContextMock.Setup(
x => x.HttpContext.User.IsInRole(It.Is<string>(s => s.Equals("admin")))
).Returns(true);
controller.ControllerContext = controllerContextMock.Object;
var viewResult = controller.Index() as ViewResult;
var model = viewResult.Model as PagedBusIdxModel;
Assert.NotNull(model);
Assert.Equal(6, model.BusAcnts.ToList().Count());
Assert.Equal("Company 2", model.BusAcnts.ToList()[1].CmpnyName);
}
有没有人知道为什么对服务的调用导致测试失败或关于如何进一步排除故障的建议?
解决方案:
感谢 Daniel J.G。问题是服务没有通过传递模拟数据库的构造函数进行初始化。改变
public BusAcntController(MyDb db) { _db = db; }
到
public BusAcntController(MyDb db) : this(new BusAcntService()) { _db = db; }
现在它通过了测试,生产应用仍然可以运行。
【问题讨论】:
-
语法高亮有问题吗?
-
您所说的“服务”是什么意思,究竟?它只是一种类吗?还是像 Web 服务这样的外部系统?
-
当这个测试失败时你会得到什么异常?
标签: c# unit-testing moq