Модульное тестирование Метод Put в Microsoft EF Web API 2

Я следую статье - http://www.asp.net/web-api/overview/testing-and-debugging/mocking-entity-framework-when-unit-testing-aspnet-web-api-2 — для модульного тестирования контроллера Web API 2.

Там автор тестирует метод put, как показано ниже:

   //....

   Product GetDemoProduct()
    {
        return new Product() { Id = 3, Name = "Demo name", Price = 5 };
    }

   [TestMethod]
    public void PutProduct_ShouldReturnStatusCode()
    {
        var controller = new ProductController(new TestStoreAppContext());

        //** Edited myself from original:
        //** var item = GetDemoProduct(); 
        var updatedItem = new Product(){ Id = 3, Name = "Demo name", Price = 6 };

        var result = controller.PutProduct(3, updatedItem) as StatusCodeResult;
        Assert.IsNotNull(result);
        Assert.IsInstanceOfType(result, typeof(StatusCodeResult));
        Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);
        //** Added a new assertion.
        Assert.AreEqual(updatedItem.Price, GetDemoProduct().Price);
    }

Какие тесты помещают метод в контроллер

public IHttpActionResult PutProduct(int id, Product product)
{

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != product.Id)
    {
        return BadRequest();
    }

    //db.Entry(product).State = EntityState.Modified;
    db.MarkAsModified(product);

     try
     {
        db.SaveChanges();
     }

     //....
     return StatusCode(HttpStatusCode.NoContent);     
}

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

Ну, фиктивный элемент данных в конце концов не обновляется. Цена остается такой же, как цена продукта до обновления.

Почему db.SaveChanges(); не работает?


person CodeSchool JJ    schedule 15.08.2014    source источник
comment
var updatedItem = new item(){ Id = у вас есть класс где-то под названием item?   -  person AaronLS    schedule 16.08.2014
comment
Извините, это была опечатка при копировании. Это должен быть экземпляр продукта.   -  person CodeSchool JJ    schedule 16.08.2014
comment
Таким образом, без утверждения updatedItem == pre-updatedItem пример модульного теста работает нормально. Он возвращает код состояния NoCoentent. Просто это на самом деле не обновляет фиктивную базу данных.   -  person CodeSchool JJ    schedule 16.08.2014


Ответы (2)


Только что узнал, что автор намеренно изменил метод saveChanges в тестовом контексте.

public class TestStoreAppContext : IStoreAppContext 
{
    public TestStoreAppContext()
    {
        this.Products = new TestProductDbSet();
    }

    public DbSet<Product> Products { get; set; }

    public int SaveChanges()
    {
        return 0;
    }
    //....

Теперь нужно подумать, почему он изменил метод saveChanges().

Спасибо вам всем.

person CodeSchool JJ    schedule 15.08.2014

1) Вы инициализируете updatedItem с ценой 6 (ваш эталонный макет имеет цену 5), передаете его методу PutProduct, но метод никогда не изменяет цену. Я не уверен, чего вы ожидали. Если вы не измените цену, очевидно, она будет такой же. Вы собирались сделать product.Price = 5 где-нибудь? В противном случае он не будет соответствовать вашему эталонному объекту и по-прежнему будет иметь цену 6.

2) Использование утверждения во входном параметре метода WebAPI — плохой тест. updatedItem на практике будет исходить из HTTP-запроса, привязанного к параметру. Независимо от того, изменяете ли вы свойства этого объекта, мало что зависит от чего-либо за пределами метода, потому что ничто другое не имеет на него ссылки. У вас есть ссылка на него в тестовом примере только потому, что именно так мы подделываем HTTP-запрос. Мы пропустили весь шаг привязки модели. На самом деле ни у кого никогда не будет ссылки на него, и это будет совершенно неактуально после завершения вызова. Вас должны интересовать только две вещи: состояние базы данных и возврат. У вас есть обратное тестирование, вы просто ошиблись с тестированием модификации базы данных. Во-первых, чтобы подчеркнуть, почему этот тест плохой:

Подумайте, не забыл ли я вызвать SaveChanges:

public IHttpActionResult PutProduct(int id, Product product)
{
     product.Price = 5;
     return StatusCode(HttpStatusCode.NoContent);     
}

Ваше утверждение будет успешным, потому что моя модификация приведет к тому, что updateProduct будет иметь цену 5 и, таким образом, будет соответствовать ссылочному объекту:

Assert.AreEqual(updatedItem.Price, GetDemoProduct().Price);

Однако реализация PutProduct неверна, так как программист забыл вызвать SaveChanges, но ваш тестовый пример пропустил это. Вам действительно нужно будет запросить базу данных через тот же контекст, чтобы увидеть, действительно ли изменения были сохранены. Вы можете сделать это с помощью реальной локальной базы данных, которая хорошо работает, если вы настроите EF для очистки БД при каждом запуске теста, или более «правильный» подход — использовать контекст, который сопоставляется с макетом в памяти. Здесь используется такой подход: http://www.c-sharpcorner.com/uploadfile/raj1979/unit-testing-in-mvc-4-using-entity-framework/

Если бы мы предположили, что у вас есть такой репозиторий, это было бы примерно так:

 [TestMethod]
 public void Create_PutProductInRepository()
 {      
     int testId = 3;     
     ...
     var result = controller.PutProduct(testId, inputProduct) as StatusCodeResult;
     ...
     // This is the kind of testing you really want to be doing.
     //  You want to "assert that the item exists in the database and has the expected value"
     Product productAfterTest = mockrepository.GetProduct(testId);
     Assert.AreEqual(inputProduct.Price, productAfterTest.Price);
 }

Очевидно, вы используете подход, у которого нет репозитория, поэтому вам придется его адаптировать. Дело в том, что утверждение должно выполнить запрос для извлечения элемента из вашего dbcontext, чтобы убедиться, что он существует, и что ожидаемые изменения были сохранены. Я не эксперт по созданию баз данных в памяти, поэтому любой источник, который у вас есть для вашего подхода, вам придется понять. Но в любом случае вы должны делать свои утверждения, переходя в контекст базы данных и извлекая элемент, чтобы убедиться, что он ДЕЙСТВИТЕЛЬНО был сохранен в контексте.

person AaronLS    schedule 15.08.2014
comment
Спасибо. Ну, я думаю, мне нужно изучить их. - person CodeSchool JJ; 22.08.2014