user13411021
user13411021

Reputation:

How to mock a service with FakeItEasy?

I have a simple entity framework repository that is accessed via a service call (please refer to this UML diagram to see the relevant classes). I am trying to test the service as follows

public class ProductServiceTests
{
    private IProductService sut;
    private IProductRepository productRepo;
    [Fact]
    public async Task CanCallGetProductById()
    {
        //Arrange
        productRepo = new Fake<IProductRepository>().FakedObject;
        sut = new ProductService(productRepo);
        var id = 1;
        var expectation = new Product { Id = id };
        A.CallTo(() => sut.GetProductById(id)).Returns(expectation);

        // Act
        var result = await sut.GetProductById(id);

        // Assert
        Assert.Equal(id, result.Id);
    }
}

However, this leads to the following exceptionSystem.ArgumentException : Object 'ProductService' of type ProductService is not recognized as a fake object.

Which implies that I probably need to use a mock of the service as follows

 sut = new Fake<IProductService>().FakedObject;

This approach, however, is also problematic: the call to the function await sut.GetProductById(id) does not actually invoke any code under test. Indeed, if I throw an exception, the test still passes

    public async Task<Product> GetProductById(int id)
    {
        throw new NotImplementedException();
        //return await _productRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id);
    }

Edit After some troubleshooting, I realized that, indeed, the code I am trying to test is not actually reached. For example, the following test

A.CallTo(() => productRepo.GetAll()).MustHaveHappened();

fails with a message that Product.GetAll() Expected to find it once or more but no calls were made to the fake object.

Can please someone explains how I should fix this error?

Edits

The IProductRepository and IProductService are defined as follows:

public interface IProductRepository
{
    IQueryable<Product> GetAll();
}
public interface IProductService
{
    Task<Product> GetProductById(int id);
}

And the ProductService is implemented as follows

public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;

    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
   

    public async Task<Product> GetProductById(int id)
    {
        //throw new NotImplementedException();
        return await _productRepository.GetAll()
                                        .FirstOrDefaultAsync(x => x.Id == id);
    }
}

Upvotes: 1

Views: 2940

Answers (1)

Blair Conrad
Blair Conrad

Reputation: 241980

The general approach when using fakes (or mocks or whatever) to help test a service is to mock the system under test's collaborators (in this case, IProductService), and to use a real system under test (if you don't use a real system under test, your system isn't "under test"). So, taking your original code and adapting, you'd want something like this:

public class ProductServiceTests
{
    private IProductService sut;
    private IProductRepository productRepo;
    [Fact]
    public async Task CanCallGetProductById()
    {
        //Arrange
        productRepo = new Fake<IProductRepository>().FakedObject;
        sut = new ProductService(productRepo);
        var id = 1;
        var expectation = new Product { Id = id };


        // This is different from your example. 
        // The idea is to configure `productRepo`, not `sut`.
        // Also, I don't know the interface for `IProductRepository`,
        // so you'll have to adjust this call to match `GetAll`
        A.CallTo(() => productRepo.GetAll(id)).Returns(new [] {expectation});

        // Act
        var result = await sut.GetProductById(id);

        // Assert
        Assert.Equal(id, result.Id);
    }
}

Upvotes: 1

Related Questions