Test unitaire : mocker un IQueryable manipulable de façon transparente

icon Tags de l'article :

Juin 28, 2018
Hello,

Petite problématique rencontrée aujourd'hui : comment tester unitairement un morceau de code de la couche métier qui ressemble à ceci :

public void RemovePicture(long groupId, long pictureId)
{
    var usersUsingThePicture = _groupDataAccess.Get().Include(p => p.User).Where(p => p.GroupId == groupId && p.User.PictureId == pictureId).Select(p => p.User).ToList();

    foreach (var user in usersUsingThePicture)
    {
        user.PictureId = null;
        _userDataAccess.Update(user);
    }

    base.SaveChanges();
    base.Remove(pictureId);
}

On voit qu'ici j'ai du code métier qui va supprimer une image utilisée par plusieurs membres, en supprimant d'abord la référence à cette image pour mes utilisateurs, avant de la supprimer de la base.

Pour faire ça, j'appelle ma couche d'accès aux données avec ma méthode Get.
Celle-ci va me renvoyer un IQueryable, IQueryable que je vais ensuite exploiter (via l'Include, le Where, le Select et le ToList) pour récupérer uniquement les utilisateurs qui ont cette image.

Sauf que voilà, dans mon test unitaire, il faut que ma couche d'accès aux données, qui est mockée, me renvoie un IQueryable...

Comment faire ça ?

En réalité, c'est ultra simple (ici avec RhinoMocks, mais applicable aux autres packages de Mocking) :

[Test]
public void GroupLogic_RemovePicture_WithUserUsingThePicture_Success()
{
    // Arrange
    long pictureId = 54689;
    long groupId = 941333;

    var userWithPicture = Builder<User>.With(p => p.PictureId = pictureId).Build();
    var groupUserWithPicture = Builder<GroupUser>.With(p => p.GroupId = groupId).With(p => p.User = userWithPicture).Build();

    var userWithoutPicture = Builder<User>.Build();
    var groupUserWithoutPicture = Builder<GroupUser>.With(p => p.GroupId = groupId).With(p => p.User = userWithoutPicture).Build();

    _groupDataAccess.Stub(p => p.Get(Arg<Expression<Func<GroupUser, object>>[]>.Is.Anything))
        .Return(new List<GroupUser>() { groupUserWithPicture, groupUserWithoutPicture }.AsQueryable());

    // Act
    _groupLogic.RemovePicture(groupId, pictureId);

    // Assert
    _userDataAccess.AssertWasCalled(p => p.Update(Arg<User>.Matches(u => u.Id == userWithPicture.Id));
    _userDataAccess.AssertWasNotCalled(p => p.Update(Arg<User>.Matches(u => u.Id == userWithoutPicture.Id));
}

Il suffit de générer ses objets avec les données nécessaires, d'en faire une collection, et de renvoyer, dans le Stub/Setup notre collection à l'aide d'un .AsQueryable().
On n'a plus derrière qu'à exploiter nos mocks en vérifiant que les appels attendus ont bien été faits.

Et voilà !

Et si besoin de mocker un dbset