Simulation d’Entity Framework lors des tests unitaires API Web ASP.NET 2

par Tom FitzMacken

Télécharger le projet terminé

Ces conseils et cette application montrent comment créer des tests unitaires pour votre application API Web 2 qui utilise Entity Framework. Il montre comment modifier le contrôleur de structure pour activer la transmission d’un objet de contexte à des fins de test, et comment créer des objets de test qui fonctionnent avec Entity Framework.

Pour une présentation des tests unitaires avec API Web ASP.NET, consultez Tests unitaires avec API Web ASP.NET 2.

Ce didacticiel part du principe que vous êtes familiarisé avec les concepts de base de API Web ASP.NET. Pour un tutoriel d’introduction, consultez Prise en main avec API Web ASP.NET 2.

Versions logicielles utilisées dans le tutoriel

Dans cette rubrique

Cette rubrique contient les sections suivantes :

Si vous avez déjà effectué les étapes du test unitaire avec API Web ASP.NET 2, vous pouvez passer à la section Ajouter le contrôleur.

Prérequis

Visual Studio 2017 Édition Communauté, Professionnel ou Entreprise

Télécharger le code

Téléchargez le projet terminé. Le projet téléchargeable inclut le code de test unitaire pour cette rubrique et pour la rubrique Test unitaire API Web ASP.NET 2.

Créer une application avec un projet de test unitaire

Vous pouvez créer un projet de test unitaire lors de la création de votre application ou ajouter un projet de test unitaire à une application existante. Ce tutoriel montre la création d’un projet de test unitaire lors de la création de l’application.

Créez une application web ASP.NET nommée StoreApp.

Dans les fenêtres Nouveau projet ASP.NET, sélectionnez le modèle Vide et ajoutez des dossiers et des références principales pour l’API web. Sélectionnez l’option Ajouter des tests unitaires . Le projet de test unitaire est automatiquement nommé StoreApp.Tests. Vous pouvez conserver ce nom.

créer un projet de test unitaire

Après avoir créé l’application, vous verrez qu’elle contient deux projets : StoreApp et StoreApp.Tests.

Créer la classe de modèle

Dans votre projet StoreApp, ajoutez un fichier de classe au dossier Models nommé Product.cs. Remplacez le contenu du fichier par le code suivant.

using System;

namespace StoreApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

Générez la solution.

Ajouter le contrôleur

Cliquez avec le bouton droit sur le dossier Contrôleurs, puis sélectionnez Ajouter et nouveau élément de structure. Sélectionnez Contrôleur d’API web 2 avec des actions, à l’aide d’Entity Framework.

ajouter un nouveau contrôleur

Définissez les valeurs suivantes :

  • Nom du contrôleur : ProductController
  • Classe de modèle : Product
  • Classe de contexte de données : [Bouton Sélectionner un nouveau contexte de données qui remplit les valeurs ci-dessous]

spécifier le contrôleur

Cliquez sur Ajouter pour créer le contrôleur avec du code généré automatiquement. Le code inclut des méthodes permettant de créer, récupérer, mettre à jour et supprimer des instances de la classe Product. Le code suivant montre la méthode d’ajout d’un produit. Notez que la méthode retourne une instance de IHttpActionResult.

// POST api/Product
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}

IHttpActionResult est l’une des nouvelles fonctionnalités de l’API Web 2 et simplifie le développement de tests unitaires.

Dans la section suivante, vous allez personnaliser le code généré pour faciliter la transmission d’objets de test au contrôleur.

Ajouter l’injection de dépendances

Actuellement, la classe ProductController est codée en dur pour utiliser un instance de la classe StoreAppContext. Vous allez utiliser un modèle appelé injection de dépendances pour modifier votre application et supprimer cette dépendance codée en dur. En cassant cette dépendance, vous pouvez passer un objet fictif lors du test.

Cliquez avec le bouton droit sur le dossier Modèles , puis ajoutez une nouvelle interface nommée IStoreAppContext.

Remplacez le code par celui-ci :

using System;
using System.Data.Entity;

namespace StoreApp.Models
{
    public interface IStoreAppContext : IDisposable
    {
        DbSet<Product> Products { get; }
        int SaveChanges();
        void MarkAsModified(Product item);    
    }
}

Ouvrez le fichier StoreAppContext.cs et apportez les modifications suivantes en surbrillance. Les modifications importantes à noter sont les suivantes :

  • La classe StoreAppContext implémente l’interface IStoreAppContext
  • La méthode MarkAsModified est implémentée
using System;
using System.Data.Entity;

namespace StoreApp.Models
{
    public class StoreAppContext : DbContext, IStoreAppContext
    {
        public StoreAppContext() : base("name=StoreAppContext")
        {
        }

        public DbSet<Product> Products { get; set; }
    
        public void MarkAsModified(Product item)
        {
            Entry(item).State = EntityState.Modified;
        }
    }
}

Ouvrez le fichier ProductController.cs. Modifiez le code existant pour qu’il corresponde au code en surbrillance. Ces modifications interrompent la dépendance à StoreAppContext et permettent à d’autres classes de passer un autre objet pour la classe de contexte. Cette modification vous permettra de réussir un contexte de test pendant les tests unitaires.

public class ProductController : ApiController
{
    // modify the type of the db field
    private IStoreAppContext db = new StoreAppContext();

    // add these constructors
    public ProductController() { }

    public ProductController(IStoreAppContext context)
    {
        db = context;
    }
    // rest of class not shown
}

Vous devez apporter une autre modification à ProductController. Dans la méthode PutProduct , remplacez la ligne qui définit l’état de l’entité sur modifié par un appel à la méthode MarkAsModified.

// PUT api/Product/5
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);
    
    // rest of method not shown
}

Générez la solution.

Vous êtes maintenant prêt à configurer le projet de test.

Installer des packages NuGet dans un projet de test

Lorsque vous utilisez le modèle Vide pour créer une application, le projet de test unitaire (StoreApp.Tests) n’inclut aucun package NuGet installé. D’autres modèles, tels que le modèle d’API web, incluent des packages NuGet dans le projet de test unitaire. Pour ce tutoriel, vous devez inclure le package Entity Framework et le package Microsoft API Web ASP.NET 2 Core au projet de test.

Cliquez avec le bouton droit sur le projet StoreApp.Tests et sélectionnez Gérer les packages NuGet. Vous devez sélectionner le projet StoreApp.Tests pour ajouter les packages à ce projet.

gérer les packages

À partir des packages en ligne, recherchez et installez le package EntityFramework (version 6.0 ou ultérieure). S’il apparaît que le package EntityFramework est déjà installé, vous avez peut-être sélectionné le projet StoreApp au lieu du projet StoreApp.Tests.

ajouter Entity Framework

Recherchez et installez le package Microsoft API Web ASP.NET 2 Core.

installer le package principal de l’API web

Fermez la fenêtre Gérer les packages NuGet.

Créer un contexte de test

Ajoutez une classe nommée TestDbSet au projet de test. Cette classe sert de classe de base pour votre jeu de données de test. Remplacez le code par celui-ci :

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;

namespace StoreApp.Tests
{
    public class TestDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T>
        where T : class
    {
        ObservableCollection<T> _data;
        IQueryable _query;

        public TestDbSet()
        {
            _data = new ObservableCollection<T>();
            _query = _data.AsQueryable();
        }

        public override T Add(T item)
        {
            _data.Add(item);
            return item;
        }

        public override T Remove(T item)
        {
            _data.Remove(item);
            return item;
        }

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

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

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

        public override ObservableCollection<T> Local
        {
            get { return new ObservableCollection<T>(_data); }
        }

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

        System.Linq.Expressions.Expression IQueryable.Expression
        {
            get { return _query.Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _query.Provider; }
        }

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

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

Ajoutez une classe nommée TestProductDbSet au projet de test qui contient le code suivant.

using System;
using System.Linq;
using StoreApp.Models;

namespace StoreApp.Tests
{
    class TestProductDbSet : TestDbSet<Product>
    {
        public override Product Find(params object[] keyValues)
        {
            return this.SingleOrDefault(product => product.Id == (int)keyValues.Single());
        }
    }
}

Ajoutez une classe nommée TestStoreAppContext et remplacez le code existant par le code suivant.

using System;
using System.Data.Entity;
using StoreApp.Models;

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

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

        public int SaveChanges()
        {
            return 0;
        }

        public void MarkAsModified(Product item) { }
        public void Dispose() { }
    }
}

Créer des tests

Par défaut, votre projet de test inclut un fichier de test vide nommé UnitTest1.cs. Ce fichier affiche les attributs que vous utilisez pour créer des méthodes de test. Pour ce tutoriel, vous pouvez supprimer ce fichier, car vous allez ajouter une nouvelle classe de test.

Ajoutez une classe nommée TestProductController au projet de test. Remplacez le code par celui-ci :

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Http.Results;
using System.Net;
using StoreApp.Models;
using StoreApp.Controllers;

namespace StoreApp.Tests
{
    [TestClass]
    public class TestProductController
    {
        [TestMethod]
        public void PostProduct_ShouldReturnSameProduct()
        {
            var controller = new ProductController(new TestStoreAppContext());

            var item = GetDemoProduct();

            var result =
                controller.PostProduct(item) as CreatedAtRouteNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(result.RouteName, "DefaultApi");
            Assert.AreEqual(result.RouteValues["id"], result.Content.Id);
            Assert.AreEqual(result.Content.Name, item.Name);
        }

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

            var item = GetDemoProduct();

            var result = controller.PutProduct(item.Id, item) as StatusCodeResult;
            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(StatusCodeResult));
            Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);
        }

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

            var badresult = controller.PutProduct(999, GetDemoProduct());
            Assert.IsInstanceOfType(badresult, typeof(BadRequestResult));
        }

        [TestMethod]
        public void GetProduct_ShouldReturnProductWithSameID()
        {
            var context = new TestStoreAppContext();
            context.Products.Add(GetDemoProduct());

            var controller = new ProductController(context);
            var result = controller.GetProduct(3) as OkNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Content.Id);
        }

        [TestMethod]
        public void GetProducts_ShouldReturnAllProducts()
        {
            var context = new TestStoreAppContext();
            context.Products.Add(new Product { Id = 1, Name = "Demo1", Price = 20 });
            context.Products.Add(new Product { Id = 2, Name = "Demo2", Price = 30 });
            context.Products.Add(new Product { Id = 3, Name = "Demo3", Price = 40 });

            var controller = new ProductController(context);
            var result = controller.GetProducts() as TestProductDbSet;

            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Local.Count);
        }

        [TestMethod]
        public void DeleteProduct_ShouldReturnOK()
        {
            var context = new TestStoreAppContext();
            var item = GetDemoProduct();
            context.Products.Add(item);

            var controller = new ProductController(context);
            var result = controller.DeleteProduct(3) as OkNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(item.Id, result.Content.Id);
        }

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

Exécuter les tests

Vous êtes maintenant prêt à exécuter les tests. Toutes les méthodes marquées avec l’attribut TestMethod seront testées. À partir de l’élément de menu Test , exécutez les tests.

exécuter des tests

Ouvrez la fenêtre Test Explorer et notez les résultats des tests.

résultats de test