web-dev-qa-db-fra.com

Se moquer en utilisant Moq en c #

J'ai le code suivant:

public interface IProductDataAccess
{
    bool CreateProduct(Product newProduct);
}

La classe ProductDataAccess implémente cette interface.

public class ProductBusiness
{
    public bool CreateProduct(Product newProduct)
    {
        IProductDataAccess pda = new ProductDataAccess();
        bool result = pda.CreateProduct(newProduct);
        return result;
    }
}

Dans ce cas, comment créer un test unitaire pour la méthode CreateProduct en se moquant de l'interface IProductDataAccess? J'ai pensé à avoir une instance publique de IProductDataAccess dans ProductBusiness et l'initialiser à l'aide de Mock<IProductDataAccess> objet mais il n'est pas recommandé d'exposer l'accès aux données à la couche d'interface utilisateur. Quelqu'un peut-il m'aider?

22
AnandhaSundari M

Exemple classique qui montre comment, si vous ne pouvez pas tester un composant particulier, RÉFACTEZ le composant!

C'est là qu'est l'amour ce que n'importe quel framework moqueur vous oblige à faire - écrire du code découplé.

Dans votre exemple, la classe ProductBusiness est très étroitement associée à la ProductDataAccess. Vous pouvez le découpler en utilisant (comme la plupart des réponses le suggèrent) une injection de dépendance. Ce faisant, vous finirez par dépendre de l'abstraction IProductDataAccess de toute implémentation concrète de celle-ci.

Un autre point à noter, lorsque vous écrivez des tests/spécifications pour la couche métier, vous voudrez généralement tester le "comportement" et non "l'état". Donc, bien que vous puissiez avoir des affirmations qui vérifient si "vrai" a été renvoyé, vos tests devraient vraiment tester si les appels d'accès aux données attendus qui ont été définis à l'aide de MOQ, nous avons réellement exécuté en utilisant l'API ".Verify" de MOQ.

Essayez d'ajouter des tests de comportement là où vous vous attendez à ce qu'une exception soit levée (à l'aide de l'API ".Throws") par la couche d'accès aux données et vérifiez si vous avez besoin d'une gestion spéciale au niveau de la couche métier.

Comme Kevin le suggère, ayez le ProductBusiness qui ressemble à:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
    bool result=_productDataAccess.CreateProduct(newProduct);
    return result;
  }
}

et utilisez n'importe quel framework de test xunit pour écrire le test comme:

 var mockDataAccess = new Mock<IProductDataAccess>();
 mockDataAccess.Setup(m => m.CreateProduct(It.IsAny<Product>())).Returns(true);
 var productBusiness = new ProductBusiness(mockDataAccess.Object);
 //behavior to be tested
36
Amol

Vous devez injecter l'interface IProductDataAccess en tant que dépendance:

public class ProductBusiness
{
    private IProductDataAccess _productDataAccess;    

    public ProductBusiness(IProductDataAccess productDataAccess)
    {
        _productDataAccess = productDataAccess;
    }

    public bool CreateProduct(Product newProduct)
    {
        bool result = _productDataAccess.CreateProduct(newProduct);
        return result;
    }
}

Ensuite, vous pouvez le remplacer par une maquette dans vos tests:

var productDataAccess = new Mock<IProductDataAccess>();
var productBusiness = new ProductBusiness(productDataAccess.Object);
22

Avec la façon dont vous avez actuellement conçu votre classe ProductBusiness, il n'y a aucun moyen de modifier l'implémentation IProductDataAccess à l'aide d'une maquette. Un modèle recommandé pour cela est injection de dépendance où vous prenez les dépendances d'un type via le constructeur. Votre classe devient donc:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
      bool result = _productDataAccess.CreateProduct(newProduct);
      return result;
  }
}

Vous êtes maintenant en mesure de tester votre classe en utilisant un framework de simulation comme moq . Par exemple:

var mockDataAccess = new Mock<IProductDataAccess>();
mockDataAccess
    .Setup(m => m.CreateProduct(It.IsAny<Product>()))
    .Returns(true);

var productBusiness = new ProductBusiness(mockDataAccess.Object);
// ... test behaviour here

Vous pouvez maintenant modifier le comportement de la maquette dans votre étape de configuration et vous assurer que votre méthode CreateProduct se comporte correctement.

Je regarderais également un framework d'injection de dépendances comme castle-windsor . Un framework d'injection de dépendances peut résoudre automatiquement les dépendances, ce qui signifie que la création d'un nouveau type est beaucoup plus facile car vous n'avez pas besoin de tout recommencer manuellement. Cela signifie également que vous pouvez changer l'implémentation utilisée en un seul endroit et qu'elle change partout.

9
Kevin Holditch

Vous ne devez pas instancier un ProductDataAccess concret à l'intérieur de votre méthode CreateProduct. Au lieu de cela, IProductDataAccess doit être un dépendance injectable. Cela peut se faire de deux manières:

Injection de propriété:

public class ProductBusiness
{
    IProductDataAccess Pda {get; set;}
}

var productBusiness = new ProductBusiness();
productBusiness.Pda = new ProductDataAccess();
productBusiness.Pda = new MockProductDataAccess();

Ou injection constructeur:

public class ProductBusiness
{
    private readonly IProductDataAccess _pda;

    public ProductBusiness(IProductDataAccess pda)
    {

        _pda = pda;
    }
}

var productBusiness = new ProductBusiness(new ProductDataAccess());
var productBusiness = new ProductBusiness(new MockProductDataAccess());

L'injection de constructeur est généralement l'approche recommandée. L'injection de propriété est utilisée pour les dépendances facultatives (par exemple, instancier un NullLogger concret par défaut dans le constructeur, et utiliser la propriété pour éventuellement injecter un enregistreur fonctionnel).

6
dcastro