user2609980
user2609980

Reputation: 10514

How to mock method with ViewModel in MVC4 with Entity Framework 4.0

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 , IGrouping2 ) at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
at System.Collections.Generic.List1..ctor(IEnumerable1 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

Answers (1)

Daniel J.G.
Daniel J.G.

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

Related Questions