Тестирование Entity Framework InMemory

В настоящее время я тестирую Entity Framework DbContext с использованием базы данных в памяти.

Чтобы сделать тесты как можно более атомарными, DbContext уникален для каждого метода тестирования и заполняется исходными данными, необходимыми для каждого теста.

Чтобы установить начальное состояние DbContext, я создал метод void SetupData, который заполняет контекст некоторыми сущностями, которые я буду использовать в тестах.

Проблема с этим подходом заключается в том, что тест не может получить доступ к объектам, созданным во время установки, потому что Entity Framework сама назначит идентификаторы, которые неизвестны до времени выполнения.

Чтобы преодолеть эту проблему, я подумал, что мой метод SetupData может стать чем-то вроде этого:

public Fixture SetupData(MyContext context) 
{
    var fixture = new Fixture();
    fixture.CreatedUser = new User();
    context.Users.Add(fixture.CreatedUser);
    context.SaveChanges();

    return fixture;
}

public class Fixture 
{
    public User CreatedUser { get; set;}
}

Как видите, он возвращает экземпляр того, что я назвал "Fixture". (Не знаю, подходит ли это имя).

Таким образом, SetupData вернет объект (Fixture) со ссылками на объекты. Таким образом, тест может использовать созданный объект. В противном случае объект будет невозможно идентифицировать, поскольку идентификатор не создается до тех пор, пока не будет вызван SaveChanges.

Мой вопрос:

  • Это плохая практика?
  • Есть ли лучший способ ссылаться на исходные данные?

person SuperJMN    schedule 19.04.2018    source источник
comment
Почему обертка? Почему не просто return context.Users.First() (или даже просто return user;)?   -  person Camilo Terevinto    schedule 19.04.2018
comment
@CamiloTerevinto Это потому, что установка вернет не только одну сущность, но и множество из них, которые могут быть интересны для проведения тестов. Например, он может иметь ExistingUser, SoftDeletedUser и UserWithNegativeBalance. Надеюсь, вы понимаете, о чем я говорю :)   -  person SuperJMN    schedule 19.04.2018
comment
Ничего плохого в этой практике (для юнит-тестов) не вижу.   -  person Evk    schedule 19.04.2018
comment
Дайте каждой сущности имя или что-то другое, чем я, вы можете использовать его для поиска в случае необходимости. Сохраняйте их как константы, которые могут использоваться вашими тестами и кодом установки тестов.   -  person ssmith    schedule 19.04.2018
comment
Поочередно укажите Id. EF будет нормально хранить его при использовании InMemory. Сохраните идентификаторы как константы, к которым могут получить доступ тесты.   -  person ssmith    schedule 19.04.2018


Ответы (2)


Это неплохая практика. На самом деле это хороший подход к созданию читаемых тестов Given-When-Then. Если вы считаете:

  • разделение вашего метода SetupData
  • переименовать его
  • возможно переход на метод расширения
public static MyContextExtensions
{
    public static User Given(this MyContext @this, User user)
    {
        @this.Users.Add(user);
        @this.SaveChanges();

        return user;
    }

    public static OtherEntity Given(this MyContext @this, OtherEntity otherEntity)
    {
         // ...
    }

    // ...
}

затем вы можете написать (концептуальный пример, детали необходимо переработать, чтобы они соответствовали вашей реализации):

[Test]
public GivenAUser_WhenSearchingById_ReturnsTheUser()
{
    var expectedUsername = "username";
    var user = _context.Given(AUser.WithName(expectedUsername));

    var result = _repository.GetUser(user.Id);

    Assert.That(result.Name, Is.EqualTo(expectedUsername));
}

... и аналогично для других сущностей.

person BartoszKP    schedule 20.04.2018

Я предпочитаю этот подход:

public void SetupData(MyContext context) 
{
    var user = new User() { Id = Fixture.TEST_USER1_ID, UserName = Fixture.TEST_USER1_NAME };
    context.Users.Add(user);
    context.SaveChanges();
}

public class Fixture 
{
    public const int TEST_USER1_ID = 123;
    public const string TEST_USER!_NAME = "testuser";
}

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

person ssmith    schedule 19.04.2018
comment
У этого будет одно небольшое предостережение — тесты будут выполняться с одинаковыми значениями. - person Fabio; 20.04.2018
comment
Я столкнулся с этой проблемой :( stackoverflow.com/questions/29632627/ - person SuperJMN; 20.04.2018
comment
Какая именно проблема? В вашем примере нет свойства name, так на какое свойство EF жалуется? Мой код не показывает изменения каких-либо записей, которые уже есть в базе данных, только новые записи. - person ssmith; 20.04.2018