Модульное тестирование с EF4 «Code First» и репозиторием - PullRequest
49 голосов
/ 12 августа 2010

Я пытаюсь разобраться в модульном тестировании очень простого тестового приложения ASP.NET MVC, которое я создал с использованием подхода Code First в последней версии EF4 CTP. Я не очень разбираюсь в юнит-тестировании / издевательствах и т.д.

Это мой класс репозитория:

public class WeightTrackerRepository
{
    public WeightTrackerRepository()
    {
        _context = new WeightTrackerContext();
    }

    public WeightTrackerRepository(IWeightTrackerContext context)
    {
        _context = context;
    }

    IWeightTrackerContext _context;

    public List<WeightEntry> GetAllWeightEntries()
    {
        return _context.WeightEntries.ToList();
    }

    public WeightEntry AddWeightEntry(WeightEntry entry)
    {
        _context.WeightEntries.Add(entry);
        _context.SaveChanges();
        return entry;
    }
}

Это IWeightTrackerContext

public interface IWeightTrackerContext
{
    DbSet<WeightEntry> WeightEntries { get; set; }
    int SaveChanges();
}

... и его реализация, WeightTrackerContext

public class WeightTrackerContext : DbContext, IWeightTrackerContext
{
    public DbSet<WeightEntry> WeightEntries { get; set; }
}

В моем тесте у меня есть следующее:

[TestMethod]
public void Get_All_Weight_Entries_Returns_All_Weight_Entries()
{
    // Arrange
    WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext());

    // Act
    List<WeightEntry> entries = repos.GetAllWeightEntries();

    // Assert
    Assert.AreEqual(5, entries.Count);
}

И мой MockWeightTrackerContext:

class MockWeightTrackerContext : IWeightTrackerContext
{
    public MockWeightTrackerContext()
    {
        WeightEntries = new DbSet<WeightEntry>();
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 });
    }

    public DbSet<WeightEntry> WeightEntries { get;set; }

    public int SaveChanges()
    {
        throw new NotImplementedException();
    }
}

Моя проблема возникает, когда я пытаюсь создать некоторые тестовые данные, так как не могу создать DbSet<>, так как у него нет конструктора. У меня такое чувство, что я лаю не то дерево, когда весь мой подход пытается насмехаться над моим контекстом. Любой совет будет приветствоваться для этого полного модульного тестирования новичка.

Ответы [ 3 ]

48 голосов
/ 12 августа 2010

Вы создаете DbSet с помощью метода Factory (Set) в контексте, но вам не нужна зависимость от EF в модульном тесте.Поэтому вам нужно посмотреть на реализацию заглушки DbSet с использованием интерфейса IDbSet или заглушку с использованием одной из сред Mocking, таких как Moq или RhinoMock.Предполагая, что вы написали свою собственную заглушку, вы просто добавили бы объекты WeightEntry во внутренний хэш-набор.

Возможно, вам больше повезет узнать о модульном тестировании EF, если вы будете искать ObjectSet и IObjectSet.Это аналоги DbSet до выхода первого CTP-кода, и о них написано гораздо больше с точки зрения модульного тестирования.

Вот отличная статья на MSDN, в которой обсуждается возможность тестирования EFкод.Он использует IObjectSet, но я думаю, что он все еще актуален.

В ответ на комментарий Дэвида я добавляю это дополнение ниже, поскольку оно не помещается в -комментарии.Не уверены, что это лучший метод для ответов на длинные комментарии?

Вам следует изменить интерфейс IWeightTrackerContext, чтобы он возвращал IDbSet из свойства WeightEntries, а не конкретный тип DbSet.Затем вы можете создать MockContext с помощью фреймворка (рекомендуется) или своей собственной заглушкой.Это вернет StubDbSet из свойства WeightEntries.

Теперь у вас также будет код (т.е. пользовательские репозитории), который зависит от IWeightTrackerContext, который в процессе работы вы передадите в свой Entity Framework WeightTrackerContext, который будет реализовывать IWeightTrackerContext.Как правило, это делается путем внедрения конструктора с использованием инфраструктуры IoC, такой как Unity.Для тестирования кода репозитория, который зависит от EF, вы должны передать свою реализацию MockContext, чтобы тестируемый код думал, что он взаимодействует с «реальным» EF и базой данных, и ведет себя (надеюсь) как и ожидалось.После того как вы удалили зависимость от изменяемой внешней системы БД и EF, вы сможете надежно проверять вызовы репозитория в своих модульных тестах.

Большая часть фреймворков предоставляет возможность проверять вызовы объектов Mock длятест поведенияВ приведенном выше примере ваш тест на самом деле только тестирует функциональность DbSet Add, которая не должна вас беспокоить, поскольку у MS для этого будут модульные тесты.То, что вы хотели бы знать, - это то, что вызов Add on DbSet был сделан из вашего собственного кода репозитория, если это уместно, и именно здесь вступают фреймворки Mock.

Извините, я знаю, что это оченьно если вы прочтете эту статью, многое станет яснее, поскольку Скотт Аллен гораздо лучше объясняет эти вещи, чем я :))

41 голосов
/ 17 февраля 2011

Основываясь на том, что сказал Daz (и, как я уже упоминал в его комментариях), вам нужно будет сделать «фальшивую» реализацию IDbSet.Пример для CTP 4 можно найти здесь , но чтобы заставить его работать, вам нужно настроить свой метод Find и добавить возвращаемые значения для пары ранее недействительных методов, таких как Add.

Вот мой собственный созданный пример для ОСАГО 5:

public class InMemoryDbSet<T> : IDbSet<T> where T : class
{
    readonly HashSet<T> _data;
    readonly IQueryable _query;

    public InMemoryDbSet()
    {
        _data = new HashSet<T>();
        _query = _data.AsQueryable();
    }

    public T Add(T entity)
    {
        _data.Add(entity);
        return entity;
    }

    public T Attach(T entity)
    {
        _data.Add(entity);
        return entity;
    }

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        throw new NotImplementedException();
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet and override Find");
    }

    public System.Collections.ObjectModel.ObservableCollection<T> Local
    {
        get { return new System.Collections.ObjectModel.ObservableCollection<T>(_data); }
    }

    public T Remove(T entity)
    {
        _data.Remove(entity);
        return entity;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    public Type ElementType
    {
        get { return _query.ElementType; }
    }

    public Expression Expression
    {
        get { return _query.Expression; }
    }

    public IQueryProvider Provider
    {
        get { return _query.Provider; }
    }
}
0 голосов
/ 12 октября 2013

Вы, вероятно, получаете ошибку

 AutoFixture was unable to create an instance from System.Data.Entity.DbSet`1[...], most likely because it has no public constructor, is an abstract or non-public type.

DbSet имеет защищенный конструктор.

Подход, обычно используемый для поддержки тестируемости, создает вашу собственную реализацию Db Set

public class MyDbSet<T> : IDbSet<T> where T : class
{

Используйте это как

public class MyContext : DbContext
{
    public virtual MyDbSet<Price> Prices { get; set; }
}

Если вы посмотрите на следующий вопрос SO, ответ 2ns, вы сможете найти полную реализацию пользовательского DbSet.

Модульное тестирование с EF4 «Code First» и репозиторием

Тогда

 var priceService = fixture.Create<PriceService>();

Не должно выдавать исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...