JelleKerkstra
JelleKerkstra

Reputation: 512

ASP.NET Generic method to mock EF6 DbSet

In my ASP.NET MVC5 web-app I'm using the EF (Model-first) for my database-communication. For unit-testing purposes, I created a generic method to generate Mock database sets. Unfortunately, I am unable to mock all the methods because when .Where(), .Any() and .Find() are used in my tested code, an exception is thrown. Can anyone help me on this? My code is below. Unittest:

[TestClass()]
public class MessagingServiceTests
{
    Mock<BoatstersEntitiesContainer> _mockContext;
    MessagingService _service;
    string _connectionId;
    Guid _userId;

    [TestInitialize()]
    public void TestInitialize()
    {
        _userId = Guid.Parse("12345678-1234-1234-1234-123412344142");
        _connectionId = "abc123";


        // Setup entities
        User user = new User { Id = _userId, CustomerId = 1 };
        Customer customer = new Customer { Id = 1, User = user, FirstName = "TestFirstName" };
        user.Customer = customer;
        Customer boatOwnerCustomer = new Customer { Id = 2, FirstName = "BoatOwner" };
        Boat boat = new Boat { Id = 1, Customer = boatOwnerCustomer, CustomerId = boatOwnerCustomer.Id };
        boatOwnerCustomer.Boats.Add(boat);


        // Init mocksets
        var userMockSet = MockDbSet.Build(new List<User> { user });
        var customerMockSet = MockDbSet.Build(new List<Customer> { customer, boatOwnerCustomer });
        var conversationMockSet = MockDbSet.Build(new List<Conversation>());
        var messageMockSet = MockDbSet.Build(new List<Message>());
        var boatMockSet = MockDbSet.Build(new List<Boat> { boat });
        var messagingHubConnectionMockSet = MockDbSet.Build(new List<MessagingHubConnection>());


        // Setup mockcontext
        _mockContext = new Mock<BoatstersEntitiesContainer>();
        _mockContext.Setup(m => m.Users).Returns(userMockSet.Object);
        _mockContext.Setup(m => m.Customers).Returns(customerMockSet.Object);
        _mockContext.Setup(m => m.Conversations).Returns(conversationMockSet.Object);
        _mockContext.Setup(m => m.Messages).Returns(messageMockSet.Object);
        _mockContext.Setup(m => m.Boats).Returns(boatMockSet.Object);
        _mockContext.Setup(m => m.MessagingHubConnections).Returns(messagingHubConnectionMockSet.Object);


        // Start service
        _service = new MessagingService(_mockContext.Object, _userId);
    }

    [TestMethod()]
    public void When_PartnerConnected_IsTrue()
    {
        Conversation conversation = new Conversation {
            Id = 1,
            Boat = _mockContext.Object.Boats.First(b => b.Id.Equals(1)),
            BoatId = _mockContext.Object.Boats.First(b => b.Id.Equals(1)).Id
        };
        conversation.Customers.Add(_mockContext.Object.Customers.First(b => b.Id.Equals(1)));
        conversation.Customers.Add(_mockContext.Object.Customers.First(b => b.Id.Equals(2)));

        MessagingHubConnection connection = new MessagingHubConnection
        {
            Id = 1,
            Connected = true,
            ConnectionId  = "abc123",
            Conversation = conversation,
            ConversationId = 1,
            Customer = _mockContext.Object.Customers.First(b => b.Id.Equals(2)),
            CustomerId = 2
        };
        conversation.MessagingHubConnections.Add(connection);

        _mockContext.Object.MessagingHubConnections.Add(connection);
        _mockContext.Object.Conversations.Add(conversation);

        var result = _service.IsPartnerConnected();
        Assert.IsTrue(result);

        // Clean up
        _mockContext.Object.Conversations.RemoveRange(_mockContext.Object.Conversations);
        _mockContext.Object.MessagingHubConnections.RemoveRange(_mockContext.Object.MessagingHubConnections);
    }
}

Generic mockset creator:

public static class MockDbSet
{
    public static Mock<DbSet<TEntity>> Build<TEntity>(List<TEntity> data) where TEntity : class
    {
        var queryable = data.AsQueryable();
        var mockSet = new Mock<DbSet<TEntity>>();
        mockSet.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockSet.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockSet.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockSet.Setup(m => m.Add(It.IsAny<TEntity>())).Callback<TEntity>(data.Add);

        return mockSet;
    }
}

Messagingservice (which is being tested)

public class MessagingService : BaseRepository<Conversation>
{
    private readonly Customer _customer;
    private MessagingHubConnection _connection;

    public MessagingService(BoatstersEntitiesContainer context, Guid userId) : base(context)
    {
        Context = context;
        _customer = Context.Customers.First(c => c.User.Id == userId);
    }


    public bool IsPartnerConnected()
    {
        // Check if partner is connected
        return Context.MessagingHubConnections.Any(c => c.ConversationId.Equals(_connection.ConversationId) && c.Customer.Id != _customer.Id && c.Connected);
    }
}

In MessagingService.IsPartnerConnected(), the following exception is thrown:

Test Name: When_PartnerConnected_IsTrue Test FullName: Boatsters.Sunshine.UnitTests.MessagingServiceTests.When_PartnerConnected_IsTrue Test Source: C:\Users\Jelle\Source\Repos\Boatsters.Sunshine\Boatsters.Sunshine.UnitTests\MessagingServiceUnitTest.cs : line 94 Test Outcome: Failed Test Duration: 0:00:00,0289022 Result StackTrace:
bij lambda_method(Closure , MessagingHubConnection ) bij System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2 predicate) bij lambda_method(Closure ) bij System.Linq.EnumerableExecutor1.Execute() bij System.Linq.EnumerableQuery1.System.Linq.IQueryProvider.Execute[S](Expression expression) bij System.Linq.Queryable.Any[TSource](IQueryable1 source, Expression1 predicate) bij Boatsters.Services.MessagingService.IsPartnerConnected() in C:\Users\Jelle\Source\Repos\Boatsters.Sunshine\Boatsters.Services\MessagingService.cs:line 156 bij xxx.MessagingServiceTests.When_PartnerConnected_IsTrue() in C:\xxx\MessagingServiceUnitTest.cs:line 118 Result Message: Test method xxx.UnitTests.MessagingServiceTests.When_PartnerConnected_IsTrue threw exception: System.NullReferenceException: Object reference not set to an instance of an object

Upvotes: 1

Views: 980

Answers (1)

Nkosi
Nkosi

Reputation: 246998

Based on review of MessagingService, it is not immediately apparent where _connection is assigned a value. Looks like that is what is null when the method under test is called based on stack trace lambda_method(Closure , MessagingHubConnection )

Also from past experiences with Moq and DbSet<>, this line needs to updated so that multiple calls can be made to the data source.

mockSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());

Change the .Returns(queryable.GetEnumerator()) to return a Func

mockSet.As<IQueryable<TEntity>>()
    .Setup(m => m.GetEnumerator())
    .Returns(() => queryable.GetEnumerator()); //<-- Note change here.

The original will return the same enumerator for every call which can only be enumerated once and may cause issues. Using the Func will allow a fresh enumerator to be return on every call to allow for multiple passes on the data source.

Upvotes: 1

Related Questions