Search code examples
testingasp.net-core-3.0mediatorfakeiteasymediatr

FakeItEasy ControllerTest HttpGet Calls


I want to start using FakeItEasy for testing queries. The tests I want to write should check if entities are returned on HttpGet calls (get all and get by Id)

The Controller:

public class ToDoController : ControllerBase
  {
    private readonly IMediator _mediator;

    public ToDoController(IMediator mediator) =>
    _mediator = mediator;

    [HttpGet]
    [Produces("application/json")]
    [ProducesResponseType(typeof(IEnumerable<ToDoItem>), (int)HttpStatusCode.OK)]
    public async Task<ActionResult<IEnumerable<ToDoItem>>> Get()
    {
        var result = await _mediator.Send(new ToDoItemsQuery(new 
                         AllToDoItems())).ConfigureAwait(false);

        if (result != null && result.Any())
        {
            return result.ToList();
        }

        throw new InvalidOperationException("TODO: error handling");
    }

    [HttpGet]
    [Route("{id}")]
    [Produces("application/json")]
    [ProducesResponseType(typeof(ToDoItem), (int)HttpStatusCode.OK)]
    public async Task<ActionResult<ToDoItem>> GetById(int itemId)
    {
      var result = await _mediator
        .Send(new ToDoItemsQuery(new ToDoItemById(itemId)))
        .ConfigureAwait(false);

      if (result != null && result.Any())
      {
        return result.FirstOrDefault(); 
      }

      throw new InvalidOperationException("TODO: error handling");
    }
  }
}

The TestClass:

public class ToDoItemControllerTests : ControllerTestBase
{
  private IMediator _mediator;

  private ToDoController _sut;

  public ToDoItemControllerTests()
  {
    _mediator = A.Fake<IMediator>();
    _sut = new ToDoController(_mediator);
  }

  [TestMethod]
  public async Task GetAllItemsAsync_SuccessTest()
  {
    A.CallTo(() => _mediator.Send(A<AllToDoItems>._, 
           A<CancellationToken>._)).Returns(A.CollectionOfFake<ToDoItem>(10));
    var result = await _sut.Get();

    Assert.IsNotNull(result);
    A.CallTo(() => _mediator).MustHaveHappened();
  }

  [TestMethod]
  public async Task GetItemByIdAsync_SuccessTest()
  {
    // Arrange
    int itemId = 2;
    var commandResult =
      new List<ToDoItem>
      {
        new ToDoItem
        {
          Id = itemId        
        };
      }

    A.CallTo(() => MediatR.Send(A<ToDoItemById>._, A<CancellationToken>._)).Returns(commandResult);

    // Act
    var result = await _sut.GetById(itemId);

    // Assert
    Assert.IsNotNull(result);
    A.CallTo(() => MediatR.Send(A<ToDoItemById>._, A<CancellationToken>._)).MustHaveHappened();
  }
}

So in the first test I set up A.CallTo the interface IMediatR to return 10 ToDoItems. During debug I see the _sut.Get() enter the controller, entering the correct method/api call. The _mediator.Send() in the controller returns a Fake IEnumerable (not the 10 items i set up in the first Call.To in the testmethod, but an enumeration that yields no results).

Because of the result.Any() being false, the controller throws an InvalidOperationException And I cannot even Assert the result.IsNotNull()

The second test I want to Test If 1 item is returned upon calling the API. I set up (a) an itemId of type int for parameter, (b) A mocked(?) List with 1 Item from the setup with the itemId and (c) a call to the mediatR should return the mocked Listfrom (b)

I make the call from the test, in debug I see the call await _mediator.Sent() returning A Fake Ienumerable of ToDoItem, result is not null, but because result.Any() is false, the item doesn't get returned, and I get another InvalidOperationException

I feel like I'm missing something in the setup of the tests.. A Fake database Interface? I don't want to chance my controller and make the if less restrictive, just so my test would pass

EDIT: Even if I change the if condition to removing the Any condition and I see the test entering the controller, returning "First Or Default" of the result, The test fails on A Call To Must Have Happened. Expected to find it once or more but no calls were made to the fake object. This I really don't get, i actually see him making the call?!

I've browsed GitHub to find examples but the closest I found was Entities with methods, with those methods being defined in an interface. This is not the case here

Seeing as the official documentation doesn't make me any wiser I turn to SO <3 Thanks in advance!


Solution

  • In the first test, you configure the call to Send with an argument of type AllToDoItems. But in the controller, you actually call Send with a TodoItemsQuery. So the call doesn't match, and the default (unconfigured) behavior, which is to return a fake IEnumerable, applies. You need to configure the call like this:

    A.CallTo(() => _mediator.Send(A<TodoItemsQuery>._, 
               A<CancellationToken>._)).Returns(A.CollectionOfFake<ToDoItem>(10));
    

    In the second test, the problem is the same, with ToDoItemById instead of AllToDoItems