Я хотел бы рассказать об этом на этом фоне. Пропустите, если хотите. Некоторое время я уделял пристальное внимание предстоящим дебатам о переполнении stackoverflow и в других местах в отношении тестирования кода в том, что касается EF. Один лагерь говорит, проверяйте непосредственно против базы данных из-за различий между Linq-объектами и Sql и реализациями. Другой говорит тест, насмехаясь.
Другим разделом мнения является проблема использования репозиториев или принятие того, что DbContext и DbSet уже предоставляют единицу работы и шаблон репозитория. В то время, когда я использовал EF, я пробовал каждую комбинацию мнений, представленных этими лагерями. Независимо от того, что я сделал, EF оказывается трудным для тестирования.
Я был в восторге от того, что команда EF сделала DbSet более макетным в EF 6. Они также предоставили документация о том, как издеваться над DbSet, включая асинхронные методы с использованием Moq. При работе над моим последним проектом с участием Web Api я понял, что если бы я мог высмеивать EF, я мог бы пропустить создание репозиториев, поскольку нормальная причина их написания - сделать что-то проверяемым. Вдохновение появилось после чтения нескольких сообщений в блоге, таких как this...
- Конец фона ---
Фактическая проблема заключается в том, что, следуя примеру кода, предоставленного командой EF о методе Moq DbSet, если .Include() используется в любом коде, генерируется исключение ArgumentNullException.
Другие связанные сообщения в формате SO
Вот мой интерфейс для DbContext:
public interface ITubingForcesDbContext
{
DbSet<WellEntity> Wells { get; set; }
int SaveChanges();
Task<int> SaveChangesAsync();
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
Это основной объект, с которым мой контроллер работает с
public class WellEntity
{
public int Id { get; set; }
public DateTime DateUpdated { get; set; }
public String UpdatedBy { get; set; }
[Required]
public string Name { get; set; }
public string Location { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<GeometryItem> GeometryItems
{
get { return _geometryItems ?? (_geometryItems = new Collection<GeometryItem>()); }
protected set { _geometryItems = value; }
}
private ICollection<GeometryItem> _geometryItems;
public virtual ICollection<SurveyPoint> SurveyPoints
{
get { return _surveyPoints ?? (_surveyPoints = new Collection<SurveyPoint>()); }
protected set { _surveyPoints = value; }
}
private ICollection<SurveyPoint> _surveyPoints;
public virtual ICollection<TemperaturePoint> TemperaturePoints
{
get { return _temperaturePoints ?? (_temperaturePoints = new Collection<TemperaturePoint>()); }
protected set { _temperaturePoints = value; }
}
private ICollection<TemperaturePoint> _temperaturePoints;
}
Вот контроллер, который напрямую использует EF DbContext
[Route("{id}")]
public async Task<IHttpActionResult> Get(int id)
{
var query = await TheContext.Wells.
Include(x => x.GeometryItems).
Include(x => x.SurveyPoints).
Include(x => x.TemperaturePoints).
SingleOrDefaultAsync(x => x.Id == id);
if (query == null)
{
return NotFound();
}
var model = ModelFactory.Create(query);
return Ok(model);
}
Наконец, это неудачный тест...
Настройка теста ---
[ClassInitialize]
public static void ClassInitialize(TestContext testContest)
{
var well1 = new WellEntity { Name = "Well 1" };
var well2 = new WellEntity { Name = "Well 2" };
var well3 = new WellEntity { Name = "Well 3" };
var well4 = new WellEntity { Name = "Well 4" };
well1.GeometryItems.Add(new GeometryItem());
well1.TemperaturePoints.Add(new TemperaturePoint());
well1.SurveyPoints.Add(new SurveyPoint());
well2.GeometryItems.Add(new GeometryItem());
well2.TemperaturePoints.Add(new TemperaturePoint());
well2.SurveyPoints.Add(new SurveyPoint());
well3.GeometryItems.Add(new GeometryItem());
well3.TemperaturePoints.Add(new TemperaturePoint());
well3.SurveyPoints.Add(new SurveyPoint());
well4.GeometryItems.Add(new GeometryItem());
well4.TemperaturePoints.Add(new TemperaturePoint());
well4.SurveyPoints.Add(new SurveyPoint());
var wells = new List<WellEntity> { well1, well2, well3, well4 }.AsQueryable();
var mockWells = CreateMockSet(wells);
_mockContext = new Mock<ITubingForcesDbContext>();
_mockContext.Setup(c => c.Wells).Returns(mockWells.Object);
}
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data) where T : class
{
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IDbAsyncEnumerable<T>>()
.Setup(m => m.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));
mockSet.As<IQueryable<T>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<T>(data.Provider));
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<T>>().Setup(m =>m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<T>>().Setup(m=>m.GetEnumerator()).
Returns(data.GetEnumerator());
return mockSet;
}
[TestMethod]
public async Task Get_ById_ReturnsWellWithAllChildData()
{
// Arrange
var controller = new WellsController(_mockContext.Object);
// Act
var actionResult = await controller.Get(1);
// Assert
var response = actionResult as OkNegotiatedContentResult<WellModel>;
Assert.IsNotNull(response);
Assert.IsNotNull(response.Content.GeometryItems);
Assert.IsNotNull(response.Content.SurveyPoints);
Assert.IsNotNull(response.Content.TemperaturePoints);
}
TestDbAsyncQueryProvider и TestDbAsyncEnumerator поступают непосредственно из документации по документации EF. Я пробовал несколько разных вариантов того, как я создаю данные для макета, им не повезло.