C#/Moq - Как заполнить многоуровневые тестовые данные?

Я новичок в Moq, и я хотел бы написать модульные тесты, используя его. У меня есть база данных с несколькими таблицами, например:

EducationUser   | Application
- UsrName         - Student
- UsrPwd          - CourseId
- UsrChallenge    - Date
- IsTeacher       - Grade
- FullName

Это база данных на localdb, над которой я хочу издеваться. Я создал объекты с помощью Entity Framework. Интерфейс этих сущностей IEducationEntities.

Теперь я хотел бы создать фиктивный объект и протестировать некоторые веб-службы, например:

    [TestMethod()]
    public void LoginTest()
    {
        HttpResponseMessage response = Request.CreateResponse(_accountController.Login("andrew", "DefaultPassword"));
        Assert.IsTrue(response.IsSuccessStatusCode, "User unable to log in with correct login info");

    }

Для этого, как я понял из документации, я должен в состоянии сделать что-то вроде:

[TestClass()]
public class AccountControllerTests : ApiController
{
    Mock<IEducationEntities> _entities = new Mock<IEducationEntities>(MockBehavior.Strict);
    private AccountController _accountController;

public AccountControllerTests() {
        _accountController = new AccountController(_entities.Object);
        _entities.Setup(table => table.EducationUsers.UsrName).Returns("andrew");
        _entities.Setup(table => table.EducationUsers.UsrPwd).Returns("DefaultPassword");
}
[TestMethod] //etc, defining tests below

Однако это вообще не работает, поскольку сущности, сгенерированные из базы данных, по-видимому, не содержат информации о подполях, и я получаю сообщение об ошибке:

«DbSet» не содержит определения для «UsrPwd», и не удалось найти метод расширения «UsrPwd», принимающий первый аргумент типа «DbSet» (вам не хватает директивы using или ссылки на сборку?)

Что мне не хватает? Как заполнить объект moq тестовыми данными, имеющими ту же структуру, что и моя база данных?


person lte__    schedule 20.05.2017    source источник
comment
В вашей примерной структуре таблицы поле пароля называется UsrPassword, но вы называете его UsrPwd в своем тестовом/фиктивном коде.   -  person Ben Rubin    schedule 20.05.2017


Ответы (1)


В этой статье описывается, как имитировать контекст Entity Framework (при условии, что вы используете версию 6 или более позднюю)

Вы сделаете что-то вроде этого:

[TestMethod]
public void TestSomething()    
{
   // Create the user data
   var educationUsers = new List<EducationUser>
   {
       new EducationUser
       {
           UsrName = "andrew",
           UsrPwd = "DefaultPassword"
       }
   }.AsQueryable();

   // Create the DbSet that contains the user data and wire it up to return the user data that was created above
   Mock<DbSet<EducationUser>> educationUsersDbSet = new Mock<DbSet<EducationUser>>();
   educationUsersDbSet.As<IQueryable<EducationUser>>().Setup(m => m.Provider).Returns(educationUsers.Provider);
   educationUsersDbSet.As<IQueryable<EducationUser>>().Setup(m => m.Expression).Returns(educationUsers.Expression);
   educationUsersDbSet.As<IQueryable<EducationUser>>().Setup(m => m.ElementType).Returns(educationUsers.ElementType);
   educationUsersDbSet.As<IQueryable<EducationUser>>().Setup(m => m.GetEnumerator()).Returns(educationUsers.GetEnumerator());

   // Create the mock context and wire up its EducationUsers property to return the DbSet that was created above
   var context = new Mock<IEducationEntities>();
   context.Setup(e => e.EducationUsers).Returns(educationUsersDbSet.Object);

   // Create the account controller using the mock DbContext
   _accountController = new AccountController(context.Object);

   // ... the rest of your testing code ...
}

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

public static Mock<DbSet<TEntity>> CreateMockDbSet<TEntity>(IQueryable<TEntity> models) where TEntity : class
{
    Mock<DbSet<TEntity>> dbSet = new Mock<DbSet<TEntity>>();

    dbSet.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(models.ElementType);
    dbSet.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(models.Expression);
    dbSet.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(models.GetEnumerator());
    dbSet.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(models.Provider);

    return dbSet;
}

Тогда ваш метод тестирования становится

[TestMethod]
public void TestSomething()    
{
   // Create the user data
   var educationUsers = new List<EducationUser>
   {
       new EducationUser
       {
           UsrName = "andrew",
           UsrPwd = "DefaultPassword"
       }
   }.AsQueryable();

   // Create the DbSet that contains the user data and wire it up to return the user data that was created above
   Mock<DbSet<EducationUser>> educationUsersDbSet = new CreateMockDbSet(educationUsers);

   // Create the mock context and wire up its EducationUsers property to return the DbSet that was created above
   var context = new Mock<IEducationEntities>();
   context.Setup(e => e.EducationUsers).Returns(educationUsersDbSet.Object);

   // Create the account controller using the mock DbContext
   _accountController = new AccountController(context.Object);

   // ... the rest of your testing code ...
}
person Ben Rubin    schedule 20.05.2017
comment
Вышеупомянутый подход был тем же, что и я, пока не начал реализовывать шаблон async/await. Если вы читаете предоставленную ссылку, это объясняет, что этот подход не работает для этого. Кроме того, подход, который они предлагают для async/await, не работает с EF Core, так что имейте в виду. Сейчас я использую UseInMemoryDatabase EF Core для модульного тестирования вплоть до уровня репозитория с большим успехом. Если вас интересует пример, я приведу его. - person MORCHARD; 23.06.2017
comment
Да, я хотел бы увидеть пример. - person Ben Rubin; 24.06.2017