Search code examples
c#.net.net-coremoqxunit

How to test business logic methods using moq and xunit?


I'm using generic repository pattern and this code from my business logic.

public class FolderManager : GenericManager<Folder>, IFolderService
{
    private readonly IGenericDal<Folder> _genericDal;
    private readonly IFolderDal _folderDal;
    public FolderManager(IFolderDal folderDal,IGenericDal<Folder> genericDal) : base(genericDal)
    {
        _genericDal = genericDal;
        _folderDal = folderDal;
    }

    public async Task<List<Folder>> GetFoldersByUserId(int id)
    {
        return await _genericDal.GetAllByFilter(I => I.AppUserId == id && I.IsDeleted == false && I.ParentFolderId==null);
    } ...another methods

IFolderService Interface :

public interface IFolderService : IGenericService<Folder>
{
    Task<List<Folder>> GetFoldersByUserId(int id);
}     ...another methods

I want to test GetFoldersByUserId(int id) method and i tried this :

public class FolderServiceTest
{  
    private readonly FolderManager _sut;
    private readonly Mock<IGenericDal<Folder>> _folderRepoMock = new Mock<IGenericDal<Folder>>();
    private readonly Mock<IFolderDal> _folderDalMock = new Mock<IFolderDal>();
    public FolderServiceTest()
    {
        _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);
    }

    [Fact]
    public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
    {
        //Arrange
        Mock<IFolderService> folderServiceMock = new Mock<IFolderService>();
        folderServiceMock.Setup(x => x.GetFoldersByUserId(It.IsAny<int>())).ReturnsAsync(GetSampleFolder);
        
        var expected = GetSampleFolder();

        //Act
         
        //returns null beacuse _sut does not work with the setup I wrote above
        //how can i test this method ? 
        var actual = await _sut.GetFoldersByUserId(1); /* */

        //Assert 
        Assert.Equal(expected.Count, actual.Count);

        for (int i = 0; i < expected.Count; i++)
        {
            Assert.Equal(expected[i].FolderName, actual[i].FolderName);
            Assert.Equal(expected[i].Size, actual[i].Size);
        } 
    }

When I start the test, the actual value is null and the test fails. GetSampleFolder method has a list of Folders and return this list. My question is how to test GetFoldersByUserId(int id) method ?


Solution

  • The test below shows how to appropriately setup mocks.

    The tricky part is the matching on expression, which is not supported by Moq. That's why I used It.IsAny matcher there.

    [Fact]
    public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
    {
        //Arrange
        var expected = GetSampleFolder();
    
        var _folderRepoMock = new Mock<IGenericDal<Folder>>();
        _folderRepoMock
            .Setup(x => x.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>()))
            .ReturnsAsync(expected);
    
        var _folderDalMock = new Mock<IFolderDal>();
    
        var _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);
    
    
        //Act
        var actual = await _sut.GetFoldersByUserId(1);
    
        //Assert 
        Assert.Equal(expected.Count, actual.Count);
    
        for (int i = 0; i < expected.Count; i++)
        {
            Assert.Equal(expected[i].FolderName, actual[i].FolderName);
            Assert.Equal(expected[i].Size, actual[i].Size);
        }
    }
    

    If you really wanted to test against the correctness of the expression which handed over to GetAllByFilter by GetFoldersByUserId you need to do some extra work. I personally used to match against the expression.ToString() result, according to your test case instead of It.IsAny...:

    Is.Is<Expression<Func<Folder, bool>>>(exp => exp.ToString() == "I => I.AppUserId == 1 && I.IsDeleted == false && I.ParentFolderId==null")
    

    But to make this to work you should partially evaluate the expression before to explicitly replace encapsulated variable references and constant references with the actual value. Without this step exp.ToString() would look like this:

    I => (((I.AppUserId == value(StackOverflow.UnitTest1+FolderManager+<>c__DisplayClass3_0).id) AndAlso (I.IsDeleted == False)) AndAlso (I.ParentFolderId == null))
    

    where things like StackOverflow.UnitTest1+FolderManager would be the parts leading the actual encapsulated id variable's place in your code.

    Instead of using the exp.ToString() method you can always modify your test to actually use the expression instead of match on it:

    // just populate to cover all special cases
    private List<Folder> testFolderList = new List<Folder>()
    {
        new Folder() { AppUserId=1, IsDeleted=false, ParentFolderId=null, FolderName = "a", Size = 1 },
        new Folder() { AppUserId=1, IsDeleted=true, ParentFolderId=null, FolderName = "b", Size = 2 },
        new Folder() { AppUserId=1, IsDeleted=false, ParentFolderId=2, FolderName = "c", Size = 3 },
        new Folder() { AppUserId=2, IsDeleted=false, ParentFolderId=null, FolderName = "a", Size = 4 },
    };
    
    
    [Fact]
    public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
    {
        //Arrange
        var userId = 1;
        var expected = testFolderList
            // replace with expression based on the _contract_ you expect from GetFoldersByUserId
            .Where(I => I.AppUserId == userId && I.IsDeleted == false && I.ParentFolderId == null) 
            .ToList();
    
        var _folderRepoMock = new Mock<IGenericDal<Folder>>();
        _folderRepoMock
            .Setup(x => x.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>()))
            .ReturnsAsync((Expression<Func<Folder, bool>> exp) =>
            {
                return testFolderList
                    // here we explicitly use the expression we got as parameter
                    .Where(exp.Compile())
                    .ToList();
            });
    
        var _folderDalMock = new Mock<IFolderDal>();
    
        var _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);
    
    
        //Act
        var actual = await _sut.GetFoldersByUserId(userId);
    
        //Assert 
        Assert.Equal(expected.Count, actual.Count);
    
        for (int i = 0; i < expected.Count; i++)
        {
            Assert.Equal(expected[i].FolderName, actual[i].FolderName);
            Assert.Equal(expected[i].Size, actual[i].Size);
        }
    }
    
    

    This way the appropriateness of the constructed expression gets tested against your expectations given the test data.