60 votes

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

Je suis très novice dans le domaine des tests unitaires et de l'imitation. J'essaie d'écrire des tests unitaires qui couvrent un 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 concrète de IRepository sous IoC, 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 de tester la logique de SignupLogic.AddNewCompany() elle-même, plutôt que la logique et le référentiel concret, je crée un modèle de IRepository et le passe à SignupLogic. Le Repository 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 dont le nom est "Company Inc". Le test unitaire qui appelle SignupLogic.AddNewCompany crée une société avec des détails en double et essaie de la transmettre, et j'affirme qu'une ArgumentException est lancée avec le message "Company already exists". Ce test ne passe pas.

En déboguant le test unitaire et AddNewCompany() lors de son exécution, il semblerait que existingCompany soit toujours nul. En désespoir de cause, j'ai trouvé que si je mets à jour SignupLogic.AddNewCompany() de sorte que l'appel à FindBy ressemble à ceci :

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

le test passe, ce qui me suggère que Moq répond uniquement au code qui est exactement le même que celui que j'ai mis en place sur mon banc d'essai. Évidemment, ce n'est pas particulièrement utile pour tester que toute entreprise dupliquée est rejetée par SignupLogic.AddNewCompany.

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

D'après tout ce que je lis, il semblerait que tester les expressions comme j'essaie de le faire ne soit pas possible avec Moq. Est-ce possible ? Merci de m'aider !

74voto

Aasmund Eldhuset Points 17036

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

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

En ... vous pouvez utiliser predicate pour retourner les entreprises correspondantes (et peut-être même lever une exception si les entreprises correspondantes ne sont pas celles que vous attendiez). Ce n'est pas très joli, mais je pense que cela fonctionnera.

7voto

Mark Coleman Points 24469

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

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

Premier test, renvoyer une entreprise sans tenir compte du prédicat qui provoquera le rejet 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 essai, faites en sorte que le type de retour soit une liste vide, ce 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());

2voto

Aliostad Points 47792

Vous ne vous moquez normalement que des types que vous possédez. Ceux que vous ne possédez pas ne doivent pas être moqués en raison de diverses difficultés. Par conséquent, l'utilisation de simulateurs d'expressions - comme l'indique le titre de votre question - n'est pas la meilleure solution.

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X