How can I stub an interface method using Moq

2019-05-21 08:19发布

Is there a way to stub out a method using Moq? I saw quite a few questions with similar titles, but none that actually answered my question. Here was a unit testing example I was given and I found it very difficult to test using Moq. What I would like to do is unit test the EmailTasks.UserIsValidForNotice() method:

public class User
{

    public DateTime JoinDate { get; set; }
    public bool Active { get; set; }
}

public class EmailTasks
{
    IUserRepository repo;
    public EmailTasks(IUserRepository repo)
    {
        this.repo = repo;
    }
    public IList<User> UserIsValidForNotice(DateTime minDate)
    {
        return repo.FindAll(u => u.Active && u.JoinDate > minDate);
    }
}

public interface IUserRepository
{
    IList<User> FindAll(Func<User, bool> q);
}

I can set up a stub like the following and then easily test the query, but I was not able to do it using Moq, because I couldn't access the method parameters in Mocks Return function.

public class StubRepo : IUserRepository
{
    public IList<User> PersonList { get; set; }

    public IList<User> FindAll(Func<User, bool> q)
    {
        return PersonList.Where(q).ToList();
    }
}

I understand that this might not be the best design, but I am just interested in if this can be done using Moq.

2条回答
beautiful°
2楼-- · 2019-05-21 08:27
var personList = new List<User>(); // set up your personList here
var repoMock = new Mock<IUserRepository>();
repoMock.Setup(repo => repo.Findall(It.IsAny<Func<User, bool>>())
    .Return((Func<user, bool> q) => personList.Where(q).ToList());
查看更多
做个烂人
3楼-- · 2019-05-21 08:44

The question here is what are you testing?

IMO, what you should be testing is whether you have called the repository with the correct Func. You could do this as follows:

[Test]
public void UserIsValidForNotice_has_correct_expression()
{
    var repoMock = new Mock<IUserRepository>();

    var sut = new EmailTasks(repoMock.Object);
    sut.UserIsValidForNotice(DateTime.Now);

    repoMock.Verify(r => r.FindAll(It.Is<Func<User, bool>>(func => func(someUser));
}

When you call the Verify method, you check whether the repository has been called. Furthermore you check whether it has been called with an instance of Func<User, bool> (that is the It.Is<> part.

For It.Is<> you can specify a callback will get the parameter that was passed and returns true when the parameter was valid and false if not. So in that callback you can execute the Func over a known user and check whether it evaluates correctly.

If you still want to stub it out and return something, you could do it like this:

repoMock.Setup(r => r.FindAll(It.IsAny<Func<User, bool>>)
        .Returns<Func<User, bool>, IList<User>>(fnc => new List<User>());

If you call the generic overload of Returns you can specify up to 8 type parameters. The first ones will be passed in to the expression you provide (from the actual call) while the last one determines the return type.

查看更多
登录 后发表回答