ASP.NET 模拟 EF6 DbSet 的通用方法
ASP.NET Generic method to mock EF6 DbSet
在我的 ASP.NET MVC5 网络应用程序中,我使用 EF(模型优先)进行数据库通信。出于单元测试的目的,我创建了一个生成模拟数据库集的通用方法。不幸的是,我无法模拟所有方法,因为在我测试的代码中使用 .Where()
、.Any()
和 .Find()
时,会抛出异常。谁可以帮我这个事?我的代码如下。
单元测试:
[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);
}
}
通用模拟集创建者:
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;
}
}
消息服务(正在测试中)
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);
}
}
在MessagingService.IsPartnerConnected()中,抛出如下异常:
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, Func
2 predicate)
bij lambda_method(Closure )
bij System.Linq.EnumerableExecutor1.Execute()
bij System.Linq.EnumerableQuery
1.System.Linq.IQueryProvider.Execute[S](Expression expression)
bij System.Linq.Queryable.Any[TSource](IQueryable1 source, Expression
1 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
根据对 MessagingService
的审查,无法立即看出 _connection
的赋值位置。当根据堆栈跟踪 lambda_method(Closure , MessagingHubConnection )
调用被测方法时,看起来这就是 null
此外,根据过去使用 Moq 和 DbSet<>
的经验,此行需要更新,以便可以对数据源进行多次调用。
mockSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
将 .Returns(queryable.GetEnumerator())
更改为 return a Func
mockSet.As<IQueryable<TEntity>>()
.Setup(m => m.GetEnumerator())
.Returns(() => queryable.GetEnumerator()); //<-- Note change here.
原版将 return 每次调用都使用相同的枚举器,只能枚举一次,可能会导致问题。使用 Func
将允许新的枚举器在每次调用时成为 return,以允许对数据源进行多次传递。
在我的 ASP.NET MVC5 网络应用程序中,我使用 EF(模型优先)进行数据库通信。出于单元测试的目的,我创建了一个生成模拟数据库集的通用方法。不幸的是,我无法模拟所有方法,因为在我测试的代码中使用 .Where()
、.Any()
和 .Find()
时,会抛出异常。谁可以帮我这个事?我的代码如下。
单元测试:
[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);
}
}
通用模拟集创建者:
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;
}
}
消息服务(正在测试中)
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);
}
}
在MessagingService.IsPartnerConnected()中,抛出如下异常:
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, Func
2 predicate) bij lambda_method(Closure ) bij System.Linq.EnumerableExecutor1.Execute() bij System.Linq.EnumerableQuery
1.System.Linq.IQueryProvider.Execute[S](Expression expression) bij System.Linq.Queryable.Any[TSource](IQueryable1 source, Expression
1 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
根据对 MessagingService
的审查,无法立即看出 _connection
的赋值位置。当根据堆栈跟踪 lambda_method(Closure , MessagingHubConnection )
null
此外,根据过去使用 Moq 和 DbSet<>
的经验,此行需要更新,以便可以对数据源进行多次调用。
mockSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
将 .Returns(queryable.GetEnumerator())
更改为 return a Func
mockSet.As<IQueryable<TEntity>>()
.Setup(m => m.GetEnumerator())
.Returns(() => queryable.GetEnumerator()); //<-- Note change here.
原版将 return 每次调用都使用相同的枚举器,只能枚举一次,可能会导致问题。使用 Func
将允许新的枚举器在每次调用时成为 return,以允许对数据源进行多次传递。