2016-07-12 11 views
2

У меня есть решение, в котором у меня есть проект Data, содержащий файл EF6 .edmx, созданный из существующей базы данных. Я разделил объекты на отдельный проект Entities и имел проект Repositories, который ссылается на них обоих.Как я могу протестировать репозиторий, который использует DbContext с NSubstitute?

Я добавил BaseRepository с некоторыми распространенными методами и хочу его протестировать. В верхней части класса выглядит следующим образом ...

public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class { 
    private readonly MyEntities _ctx; 
    private readonly DbSet<T> _dbSet; 

    public BaseRepository(MyEntities ctx) { 
    _ctx = ctx; 
    _dbSet = _ctx.Set<T>(); 
    } 

    public IEnumerable<T> GetAll() { 
    return _dbSet; 
    } 

    //... 
} 

После кода я нашел в https://stackoverflow.com/a/21074664/706346, я попробовал следующее ...

[TestMethod] 
public void BaseRepository_GetAll() { 
    IDbSet<Patient> mockDbSet = Substitute.For<IDbSet<Patient>>(); 
    mockDbSet.Provider.Returns(GetPatients().Provider); 
    mockDbSet.Expression.Returns(GetPatients().Expression); 
    mockDbSet.ElementType.Returns(GetPatients().ElementType); 
    mockDbSet.GetEnumerator().Returns(GetPatients().GetEnumerator()); 
    MyEntities mockContext = Substitute.For<MyEntities>(); 
    mockContext.Patients.Returns(mockDbSet); 

    BaseRepositoryInterface<Patient> patientsRepository 
          = new BaseRepository<Patient>(mockContext); 
    List<Patient> patients = patientsRepository.GetAll().ToList(); 
    Assert.AreEqual(GetPatients().Count(), patients.Count); 
} 

private IQueryable<Patient> GetPatients() { 
    return new List<Patient> { 
    new Patient { 
     ID = 1, 
     FirstName = "Fred", 
     Surname = "Ferret" 
    } 
    } 
    .AsQueryable(); 
} 

Обратите внимание, что я изменил файл TT контекста использовать IDbSet, как предложил Стюарт Клемент в своем комментарии от 4 декабря 15:15 в 22:41

Однако, когда я запускаю этот тест, он дает исключение для ссылочной ссылки, поскольку строка в конструкторе базового репозитория, которая устанавливает _dbSet, оставляет это null ...

_dbSet = _ctx.Set<T>(); 

Я бы предположил, что мне нужно добавить еще одну строку, когда я настроил свой макет контекста, но я не уверен, что. Я думал, что кода выше должно быть достаточно, чтобы заполнить DbSet.

Любой, кто может объяснить, что я пропустил или сделал неправильно?

ответ

3

Ну, проехав себя с ума, пытаясь г О, как я показал в своем вопросе, я наткнулся на Effort, который был разработан для этой задачи, и последовал за this tutorial, который заставил меня двигаться. У меня было несколько проблем с его кодом, о чем я расскажу ниже.

Кратко, что я сделал ...

*) Установите Effort.EF6 в тестовом проекте. Сначала я сделал ошибку и установил Effort (без бит EF6) и имел всевозможные проблемы. Если вы используете EF6 (или EF5, я думаю), убедитесь, что вы установили эту версию.

*) Изменен файл MyModel.Context.tt, чтобы включить дополнительный конструктор, который принял DbConnection ...public MyEntities(DbConnection connection) : base(connection, true) { }

*) Добавлена ​​строка подключения к файлу App.Config тестового проекта. Я скопировал этот стенограмм из проекта данных.

*) Добавлен метод инициализации в тестовом классе, чтобы настроить контекст ...

private MyEntities _ctx; 
private BaseRepository<Patient> _patientsRepository; 
private List<Patient> _patients; 

[TestInitialize] 
public void Initialize() { 
    string connStr = ConfigurationManager.ConnectionStrings["MyEntities"].ConnectionString; 
    DbConnection connection = EntityConnectionFactory.CreateTransient(connStr); 
    _ctx = new MyEntities(connection); 
    _patientsRepository = new BaseRepository<Patient>(_ctx); 
    _patients = GetPatients(); 
} 

Важно - В связанной статье он использует DbConnectionFactory.CreateTransient(), который дал исключение, когда я пытался выполните тесты. Кажется, что это для Code First, и поскольку я использую Model First, мне пришлось изменить его, чтобы вместо этого использовать EntityConnectionFactory.CreateTransient().

*) Фактическое испытание было довольно простым. Я добавил некоторые вспомогательные методы, чтобы попытаться привести его в порядок и сделать его более многоразовым. Я, вероятно, сделать еще несколько раундов, прежде чем рефакторинга я сделал, но это работает, и достаточно чистый сейчас ...

[TestMethod] 
public void BaseRepository_Update() { 
    AddAllPatients(); 
    Assert.AreEqual(_patients.Count, _patientsRepository.GetAll().Count()); 
} 

#region Helper methods 

private List<Patient> GetPatients() { 
    return Enumerable.Range(1, 10).Select(CreatePatient).ToList(); 
} 

private static Patient CreatePatient(int id) { 
    return new Patient { 
    ID = id, 
    FirstName = "FirstName_" + id, 
    Surname = "Surname_" + id, 
    Address1 = "Address1_" + id, 
    City = "City_" + id, 
    Postcode = "PC_" + id, 
    Telephone = "Telephone_" + id 
    }; 
} 

private void AddAllPatients() { 
    _patients.ForEach(p => _patientsRepository.Update(p)); 
} 

#endregion 

бит, который был нужен ум сдвиг здесь было то, что с трудом, в отличие от других насмешек, вы не говорите насмешливую структуру, что нужно возвращать для конкретного аргумента. Вместо этого вы должны думать об Effort как о реальной базе данных, хотя и временную в памяти. Поэтому я установил список макетов пациентов в инициализации, добавил их в базу данных и только после этого сделал фактическое тестирование.

Надеюсь, это поможет кому-то. Это оказалось намного проще, чем то, как я пытался это сделать изначально.

1

Я создал расширение NSubstitute, чтобы помочь модулю протестировать слой репозитория, вы можете найти его на GitHub DbContextMockForUnitTests. Основной файл, который вы хотите использовать, - DbContextMockForUnitTests/MockHelpers/MockExtension.cs (имеет 3 зависимых файла кода в той же папке, что и для тестирования с async), скопируйте и вставьте все 4 файла в ваш проект. Вы можете увидеть этот модульный тест, который показывает, как его использовать. DbContextMockForUnitTests/DbSetTests.cs.

Чтобы сделать относящийся к вашему коду, предположим, что вы скопировали основной файл и указали правильное пространство имен в своих операциях using. Ваш код будет что-то вроде этого (Если MyEntities не запечатан вам не нужно, чтобы изменить его, но я все равно, как общее правило кодирования пытаются принять наименьшее конкретный тип возможного):

// Slight change to BaseRepository, see comments 
public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class { 
    private readonly DbContext _ctx; // replaced with DbContext as there is no need to have a strong reference to MyEntities, keep it generic as possible unless there is a good reason not to 
    private readonly DbSet<T> _dbSet; 

    // replaced with DbContext as there is no need to have a strong reference to MyEntities, keep it generic as possible unless there is a good reason not to 
    public BaseRepository(DbContext ctx) { 
     _ctx = ctx; 
     _dbSet = _ctx.Set<T>(); 
    } 

    public IEnumerable<T> GetAll() { 
     return _dbSet; 
    } 

    //... 
} 

Unit тестовый код:

// unit test 
[TestMethod] 
public void BaseRepository_GetAll() { 
    // arrange 

    // this is the mocked data contained in your mocked DbContext 
    var patients = new List<Patient>(){ 
     new Patient(){/*set properties for mocked patient 1*/}, 
     new Patient(){/*set properties for mocked patient 2*/}, 
     new Patient(){/*set properties for mocked patient 3*/}, 
     new Patient(){/*set properties for mocked patient 4*/}, 
     /*and more if needed*/ 
    }; 
    // Create a fake/Mocked DbContext 
    var mockedContext = NSubstitute.Substitute.For<DbContext>(); 
    // call to extension method which mocks the DbSet and adds it to the DbContext 
    mockedContext.AddToDbSet(patients); 

    // create your repository that you want to test and pass in the fake DbContext 
    var repo = new BaseRepository<Patient>(mockedContext); 

    // act 
    var results = repo.GetAll(); 

    // assert 
    Assert.AreEqual(results.Count(), patients.Count); 
} 

Отказ от ответственности - я являюсь автором вышеупомянутого хранилища, но он был частично основан на Testing with Your Own Test Doubles (EF6 onwards)