Search code examples
c#.netunit-testingxunit

Fakeiteasy method not being called. errors on MustHaveHappened assertion


New in using FakeItEasy. I'm getting error at

            A.CallTo(() => Lister.GetResult(A<int>._, A<int>._)).MustHaveHappened();

so I guess this

            A.CallTo(() => Lister
                .GetResult(A<int>._, A<int>._))
                .Returns(new ResponseList(1, new List<RecordsValues>())) ;

is not being triggered. When I removed the .MustHaveHappened assertion, the test proceeds, also getting 200 statuscode (a controller is in test). It passes on the 2nd code snippet above but throws no error.

Any advice is appreciated.

Test


    public class GetByListTests
    {
        [Fact]
        public void GetByList_GetFieldsByRange_Test()
        {
            ILister Lister = A.Fake<ILister>();
            IDbConnectionFactory dbConnectionFactory = A.Fake<IDbConnectionFactory>();
            GetByList GetByList = new(dbConnectionFactory);

            A.CallTo(() => Lister
                .GetResult(A<int>._, A<int>._))
                .Returns(new ResponseList(1, new List<RecordsValues>()));
            
            //result contains empty and 0 values, with 200 statuscode
            //result it does not contain the expected return declared on the above callTo return
            var result = (OkObjectResult)GetByList.GetFieldsByRange();


            //if I remove this, the test passes but with incorrect result value, so I assume it really is not being called as when I add the line below, it errs out
            A.CallTo(() => Lister.GetResult(A<int>._, A<int>._)).MustHaveHappened();
            Assert.NotNull(result);
            Assert.Equal(200, result.StatusCode);


        }
    }

Endpoint

        private readonly ILister Lister;

        public GetByList(IDbConnectionFactory dbConnectionFactory)
        {
            DataService securityProjectDataService = new(dbConnectionFactory);
            Lister = new SecurityProjectLister(DataService);
        }

        [HttpGet]
        public IActionResult GetFieldsByRange([FromQuery][Range(0, int.MaxValue)] int skip = 0, [FromQuery][Range(0, 1000)] int top = 1000)
        {
            ResponseList response = securityProjectLister.GetResult(skip, top);
            return Ok(response);
        }

If I lack something, please point out. I'm happy to take any learnings. Thanks and cheers!


Solution

  • I think the main issue is that you are never actually using the ILister fake.

    The fake you have created resides inside your test method,

    ILister Lister = A.Fake<ILister>();
    

    whereas you create a separate ILister object in your endpoint class:

    Lister = new SecurityProjectLister(DataService);
    

    In order to actually benefit from your ILister fake, you need dependency injection of an ILister object in your enpoint class.

    As it currently stands, I find your endpoint class snippet a little difficult to understand. I believe some names have been mixed up, so that the current implementation:

    private readonly ILister Lister;
    
    public GetByList(IDbConnectionFactory dbConnectionFactory)
    {
        DataService securityProjectDataService = new(dbConnectionFactory);
        Lister = new SecurityProjectLister(DataService);
    }
    
    [HttpGet]
    public IActionResult GetFieldsByRange(...)
    {
        ResponseList response = securityProjectLister.GetResult(skip, top);
        return Ok(response);
    }
    

    should in fact be this:

    private readonly ILister securityProjectLister; // EDITED
    
    public GetByList(IDbConnectionFactory dbConnectionFactory)
    {
        DataService securityProjectDataService = new(dbConnectionFactory);
        securityProjectLister = new SecurityProjectLister(securityProjectDataService); // EDITED
    }
    
    [HttpGet]
    public IActionResult GetFieldsByRange(...)
    {
        // Now, your ILister object is used here:
        ResponseList response = securityProjectLister.GetResult(skip, top);
        return Ok(response);
    }
    

    If this is the case, I actually believe you will need to extract the creation of both the DataService object and the ILister object to outside of your endpoint class, and provide your endpoint class constructor with an ILister object.

    I believe a possible implementation could be similar to the following:

    Endpoint class snippet:

    private readonly ILister securityProjectLister;
    
    public GetByList(ILister lister)
    {
        securityProjectLister = lister;
    }
    
    [HttpGet]
    public IActionResult GetFieldsByRange(...)
    {
        // Remains unchanged
    }
    

    Test method:

    public void GetByList_GetFieldsByRange_Test()
    {
        IDbConnectionFactory dbConnectionFactory = A.Fake<IDbConnectionFactory>();
    
        DataService securityProjectDataService = new(dbConnectionFactory);
        ILister Lister = new SecurityProjectLister(securityProjectDataService);
    
        GetByList GetByList = new(Lister);
    
        // Unchanged:
        A.CallTo(() => Lister
            .GetResult(A<int>._, A<int>._))
            .Returns(new ResponseList(1, new List<RecordsValues>()));
        
        // The remaining code remains unchanged
    }
    

    Now, the GetByList object is using the very ILister object on which you have configured A.CallTo(...).Returns(...); in your test method.