I’m retrofitting production code with unit testing on my BusAcnts controller. The view contains a WebGrid and I’m using Stuart Leeks WebGrid service code (_busAcntService.GetBusAcnts) to handle the paging and sorting.
The Unit test fails with a “System.NullReferenceExceptionObject reference not set to an instance of an object.” error. If I run the test in debug and put a breakpoint at the point the service is called in the controller and another one in the Service on the called method (GetBusAcnts) and try to step through the test fails (with the same NullReference error) at the point the service is called. I cannot step into the service to see what the source of the problem is.
For testing purposes I pulled the basic query out of the service and put it in a GetBusAcnts method in the controller to emulate most of the function of the service. When I call the GetBusAcnts method in the controller rather than the one in the service the test passes.
This is a MVC5 EF6 application using xUnit 1.9.2, Moq 4.2. The EF6 mock database is set up as in this article Testing with a mocking framework (EF6 onwards). For this post I've simplified the code where I could and have not included things that are working and don't need to be shown.
I’m stumped as to why the test is failing at the point the service is called and don’t know how to troubleshoot further since I can’t step through the code.
Service Interface:
public interface IBusAcntService
{
IEnumerable<BusIdxVm> GetBusAcnts(MyDb dbCtx, out int totalRecords,
int pageSize = -1, int pageIndex = -1, string sort = "Name",
SortDirection sortOrder = SortDirection.Ascending);
}
Service:
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(); }
}
}
Controller:
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();
}
}
Unit Test:
[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);
}
Does anyone have any idea why the call to the service is making the test fail or suggestions on how I might troubleshoot further?
Solution:
Thanks to Daniel J. G. The problem was that the service was not being initialized with the constructor passing the mock db. Change
public BusAcntController(MyDb db) { _db = db; }
to
public BusAcntController(MyDb db) : this(new BusAcntService()) { _db = db; }
It now passes the test and the production app still works.