2

I'm writing a test that verifies if my controller calls my query with expected query arguments. This is my query class:

public class GetProducts : IRequest<IEnumerable<Product>>
{
    public int CategoryId { get; set; }
}

This implements IRequest<T> MediatR interface.

This is the test case that does not work:

[Theory]
[InlineData(1)]
public async Task GetProductsAsync_GivenValidRequestParameters_ReturnsListGetProductsResponseAsync(int categoryId)
{
    var expectedQuery = new GetProducts
    {
        CategoryId = categoryId
    };

    _mediatorMock
        .Setup(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()))
        .Callback<GetProducts, CancellationToken>((query, ct) => query.Should().BeEquivalentTo(expectedQuery))
        .ReturnsAsync(Enumerable.Empty<Product>());

    var response = await _productsController.GetProductsAsync(categoryId);

    response.Result.Should().BeOfType<OkObjectResult>();
    _mediatorMock.Verify(s => s.Send(It.IsAny<GetProducts>(), It.IsAny<CancellationToken>()), Times.Once);
}

This is the controller I'm testing:

[ApiController]
[Route("categories/{categoryId:int}/products")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[TrackUsage]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<GetProductsResponse>>> GetProductsAsync([FromRoute]int categoryId)
    {
        var query = new GetProducts
        {
            CategoryId = categoryId
        };

        var products = await _mediator.Send(query);

        return Ok(products.ToResponse());
    }
}

It complains because it cannot find an callback with <GetProducts, CancellationToken> as parameters even though it seems right.

I know I could use It.Is<...>(callback => true) to check each and every property, but there could be queries with multiple properties and I'd prefer to test that using FluentAssertion.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Evaldas Buinauskas
  • 13,739
  • 11
  • 55
  • 107

1 Answers1

3

Generic Send definition

public interface IMediator
{
    /// <summary>
    /// Asynchronously send a request to a single handler
    /// </summary>
    /// <typeparam name="TResponse">Response type</typeparam>
    /// <param name="request">Request object</param>
    /// <param name="cancellationToken">Optional cancellation token</param>
    /// <returns>A task that represents the send operation. The task result contains the handler response</returns>
    Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);

    //...

Source

The Callback needs to match the provided definition

//...

_mediatorMock
    .Setup(s => s.Send<IEnumerable<Product>>(It.IsAny<IRequest<IEnumerable<Product>>>(), It.IsAny<CancellationToken>()))
    .Callback<IRequest<IEnumerable<Product>>, CancellationToken>((query, ct) => 
        ((GetProducts)query).Should().BeEquivalentTo(expectedQuery)
    )
    .ReturnsAsync(Enumerable.Empty<Product>());

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472