Reputation: 10514
I want to test the following method:
public ActionResult Index()
{
var transactions = db.Transactions.Include(t => t.User)
.GroupBy(t => t.UserId)
.Select(group => new TransactionViewModel
{
User = group.FirstOrDefault().User.FullName,
UserId = group.FirstOrDefault().UserId,
Total = (group.Sum(t => t.TransactionAmount))
});
// Show lowest balance first
return View(transactions.ToList());
}
Here the Transaction
model has a list of Orders
, has a foreign key to User
and some more properties, see:
public class Transaction
{
public int TransactionId { get; set; }
public DateTime Date { get; set; }
public int UserId { get; set; }
public List<Order> Orders { get; set; }
public decimal TransactionAmount { get; set; }
public virtual User User { get; set; }
}
The TransactionViewModel
looks as follows:
public class TransactionViewModel
{
public string User { get; set; }
public int UserId { get; set; }
public decimal Total { get; set; }
}
and is used to calculate the Total
of different transactions belonging to a user.
To test this method I have a FakeDbSet
and use a FakeContext
(which both work in tests of other controllers) in the following Setup:
[TestClass]
public class TransactionControllerTest
{
TransactionController trController;
[TestInitialize]
public void TransactionControllerTestInitialize()
{
// Arrange
var memoryTransactionItems = new FakeDbSet<Transaction>
{
new Transaction {
Date = DateTime.Today,
TransactionAmount = 5.10M,
UserId = 1,
Orders = new List<Order>{
// Categorie 2 and confirmed
new Order { OrderId = 2,
UnitPrice = 2.00M,
Quantity = 1,
Date = DateTime.Today,
IsConfirmed = true,
User = new User {
Name = "Kees",
FullName="Kees Piet",
Email = "[email protected]",
isAvailable = true,
UserId = 1
},
Product = new Product {
Category = new Category {
CategoryId = 2,
Name = "Categorie2"
},
Name = "Testproduct2",
Price = 2.00M,
Visible = true
}
},
// Categorie 2 and confirmed
new Order { OrderId = 2,
UnitPrice = 1.00M,
Quantity = 1,
Date = DateTime.Today,
IsConfirmed = true,
User = new User {
Name = "Jan",
FullName="Jan Piet",
Email = "[email protected]",
isAvailable = true,
UserId = 2
},
Product = new Product {
Category = new Category {
CategoryId = 2,
Name = "Categorie2"
},
Name = "Testproduct2",
Price = 3.10M,
Visible = true
}
}
}
}
};
// Create mock units of work
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Transactions).Returns(memoryTransactionItems);
// Setup controller
trController = new TransactionController(mockData.Object);
}
[TestMethod]
public void TestTransactionIndex()
{
// Invoke
var viewResult = trController.Index() as ViewResult;
var transactionsFromView = (IEnumerable<TransactionViewModel>)viewResult.Model;
// Assert
Assert.AreEqual(1, transactionsFromView.Count(),
"The amount of transactions added to the Index View should be 1.");
}
}
When I run the TestTransactionIndex
I get the following error:
Test Name: TestTransactionIndex Test Outcome: Failed Test Duration: 0:00:30.6276475
Result Message: Test method Tests.Controllers.TransactionControllerTest.TestTransactionIndex threw exception: System.NullReferenceException: Object reference not set to an instance of an object. Result StackTrace: at lambda_method(Closure , IGrouping
2 ) at System.Linq.Enumerable.WhereSelectEnumerableIterator
2.MoveNext()
at System.Collections.Generic.List1..ctor(IEnumerable
1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) at Controllers.TransactionController.Index()
I find this strange since I setup my mock units in the proper way. I hope someone can explain how I can properly send the FakeDbSet<Transaction>
to the view and not get a NullReferenceException
.
/Edit As requested, here are the contructors for TransactionController
:
private IContext _context;
public TransactionController()
{
_context = new Context();
}
public TransactionController(IContext context)
{
_context = context;
}
Upvotes: 1
Views: 256
Reputation: 35012
The query in your index method includes the line:
db.Transactions.Include(t => t.User)
And the Select part of the query is using the User
property in the Transaction
class to populate the TransactionViewModel
as in
User = group.FirstOrDefault().User.FullName,
That line will throw a NullReferenceException if the User property in the Transaction is null. So you need the result of that query to contain a not null User property when executed in your unit test using the fake objects.
I am not sure how your fake context and DbSets work, but the easiest thing to try is to populate the User property of the Transactions in your fake memoryTransactionItems
.
You may also try adding a fake users dbset as in the next code snippet (I'm assuming you have a Users DbSet in your EF context):
var memoryUsers = new FakeDbSet<User>
{
new User{ UserId = 1, ... },
...
};
mockData.Setup(m => m.Users).Returns(memoryUsers);
Upvotes: 1