web-dev-qa-db-fra.com

Méthodes de Moq'ing où Expression <Func <T, bool >> sont passées en paramètres

Je suis très nouveau dans les tests unitaires et les moqueries! J'essaie d'écrire des tests unitaires qui couvrent du code qui interagit avec un magasin de données. L'accès aux données est encapsulé par IRepository:

interface IRepository<T> {
    ....
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    ....
}

Le code que j'essaie de tester, en utilisant une implémentation IoC'd concrète d'IRepository ressemble à ceci:

public class SignupLogic {
    private Repository<Company> repo = new Repository<Company>();

    public void AddNewCompany(Company toAdd) {
        Company existingCompany = this.repo.FindBy(c => c.Name == toAdd.Name).FirstOrDefault();

        if(existingCompany != null) {
            throw new ArgumentException("Company already exists");
        }

        repo.Add(Company);
        repo.Save();
    }
}

Afin que je teste la logique de SignupLogic.AddNewCompany () elle-même, plutôt que la logique et le référentiel concret, je moque IRepository et le passe dans SignupLogic. Le référentiel simulé ressemble à ceci:

Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....

qui renvoie un IEnumberable en mémoire contenant un objet Company avec un nom défini sur "Company Inc". Le test unitaire qui appelle SignupLogic.AddNewCompany met en place une société avec des détails en double et essaie de transmettre cela, et j'affirme qu'une ArgumentException est levée avec le message "La société existe déjà". Ce test ne passe pas.

Débogage via le test unitaire et AddNewCompany () lors de son exécution, il semblerait que existingCompany soit toujours null. En désespoir de cause, j'ai constaté que si je mettais à jour SignupLogic.AddNewCompany () pour que l'appel à FindBy ressemble à ceci:

Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();

le test réussit, ce qui me suggère que Moq ne répond qu'au code exactement identique à celui que j'ai configuré dans mon appareil de test. Évidemment, cela n'est pas particulièrement utile pour tester que toute entreprise en double est rejetée par SignupLogic.AddNewCompany.

J'ai essayé de configurer moq.FindBy (...) pour utiliser "Is.ItAny", mais cela ne fait pas non plus passer le test.

D'après tout ce que je lis, il semblerait que tester les expressions comme j'essaie ne soit pas réellement faisable avec Moq ici. C'est possible? Aidez-moi!

59
jonsidnell

Il est probablement correct que seul un Expression avec exactement la même structure (et les valeurs littérales) correspondra. Je vous suggère d'utiliser la surcharge de Returns() qui vous permet d'utiliser les paramètres avec lesquels la maquette est appelée:

repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())
        .Returns((Expression<Func<Company, bool>> predicate) => ...);

Dans ..., vous pouvez utiliser predicate pour renvoyer les sociétés correspondantes (et peut-être même lever une exception si les sociétés correspondantes ne correspondent pas à vos attentes). Pas très joli, mais je pense que ça va marcher.

73
Aasmund Eldhuset

Vous devriez pouvoir utiliser It.IsAny<>() pour accomplir ce que vous cherchez à faire. Avec l'utilisation de It.IsAny<>(), vous pouvez simplement ajuster le type de retour de votre configuration pour tester chaque branche de votre code.

It.IsAny<Expression<Func<Company, bool>>>()

Premier test, renvoyez une entreprise quel que soit son prédicat, ce qui entraînera la levée de l'exception:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>{new Company{Name = "Company Inc"}});
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
//Assert the exception was thrown.

Deuxième test, faites du type de retour une liste vide qui provoquera l'appel de add .:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>());
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
repoMock.Verify(r => r.Add(It.IsAny<Company>()), Times.Once());
7
Mark Coleman

Normalement, vous vous moquez uniquement des types que vous possédez. Ceux que vous ne possédez pas ne devraient vraiment pas être moqués en raison de diverses difficultés. Donc, les expressions moqueuses - comme l'indique le nom de votre question - ne sont pas la voie à suivre.

Dans le cadre Moq. Il est important de mettre .Returns() pour les fonctions sinon il ne correspond pas. Donc, si vous ne l'avez pas fait, c'est votre problème.

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....
2
Aliostad