最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Unit Tests the endpoint written using FastEndpoint in .Net Core 9 - Stack Overflow

programmeradmin2浏览0评论

I have written an endpoint using FastEndpoint library in .Net Core 9. I also have written the Integration test using FastEndpoint.Testing library and it is working fine.

Now i want to write the unit test my endpoint written using FastEndpoint. Below is my endpoint

public class GetAllEndpoint(AppDbContext db) :EndpointWithoutRequest<List<CategoryDto>>
{
   public override void Configure()
   {
     Get("api/category");
     AllowAnonymous();
     Summary(x =>
     {
        x.Summary = "Get All Categories";
        x.Description = "Fetches a list of all categories.";
     });
 }

 public override async Task HandleAsync(CancellationToken c)
 {
    var categories = await db.Categories
        .Select(category => new CategoryDto(category.Name , category.Description))
        .ToListAsync(c);

    await SendAsync(categories, StatusCodes.Status200OK, c);
 }

}

I am facing multiple problems here.

  1. The main challenge in testing FastEndpoints lies in correctly mocking DbSet and its asynchronous methods like ToListAsync, as they rely on IQueryable and IAsyncEnumerable.

  2. Additionally, mocking base class methods like SendAsync is difficult since they are not virtual, requiring the use of Received() to verify method calls instead of direct mocking.

Below are my Test case:

public class GetAllEndpointTestsNSubstitute{

 private readonly GetAllEndpoint _endpoint;
 private readonly AppDbContext _mockDbContext;
 private readonly DbSet<Category> _mockCategoriesDbSet;
 private readonly List<Category> _categories;

public GetAllEndpointTestsNSubstitute()
{
    _categories = new List<Category>
        {
            new Category { Name = "Category1", Description = "Description1" },
            new Category { Name = "Category2", Description = "Description2" }
        };

    // Mocking IQueryable<Category> for DbSet<Category>
    var categoryQueryable = _categories.AsQueryable();

    _mockCategoriesDbSet = Substitute.For<DbSet<Category>, IQueryable<Category>>();
    ((IQueryable<Category>)_mockCategoriesDbSet).Provider.Returns(categoryQueryable.Provider);
    ((IQueryable<Category>)_mockCategoriesDbSet).Expression.Returns(categoryQueryable.Expression);
    ((IQueryable<Category>)_mockCategoriesDbSet).ElementType.Returns(categoryQueryable.ElementType);
    ((IQueryable<Category>)_mockCategoriesDbSet).GetEnumerator().Returns(categoryQueryable.GetEnumerator());

    // Mock the ToListAsync method to return the list of categories
    _mockCategoriesDbSet.ToListAsync(Arg.Any<CancellationToken>())
        .Returns(callInfo => Task.FromResult(_categories.ToList()));

    // Mock the DbContext to return the mock DbSet<Category>
    _mockDbContext = Substitute.For<AppDbContext>();
    _mockDbContext.Categories.Returns(_mockCategoriesDbSet);

    // Instantiate the endpoint with the mocked DbContext
    _endpoint = new GetAllEndpoint(_mockDbContext);
}

[Fact]
public async Task HandleAsync_ShouldReturnCategories()
{
    // Arrange
    var cancellationToken = new CancellationToken();

    // Act
    await _endpoint.HandleAsync(cancellationToken);

    // Assert
    var expectedCategories = _categories
        .Select(c => new CategoryDto(c.Name, c.Description))
        .ToList();

    // Check that the count is correct
    Assert.Equal(2, expectedCategories.Count);

    // Check that the expected category names match
    Assert.Equal("Category1", expectedCategories[0].Name);
    Assert.Equal("Category2", expectedCategories[1].Name);
}

}

Below is the error

NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException : Could not find a call to return from.

I have written an endpoint using FastEndpoint library in .Net Core 9. I also have written the Integration test using FastEndpoint.Testing library and it is working fine.

Now i want to write the unit test my endpoint written using FastEndpoint. Below is my endpoint

public class GetAllEndpoint(AppDbContext db) :EndpointWithoutRequest<List<CategoryDto>>
{
   public override void Configure()
   {
     Get("api/category");
     AllowAnonymous();
     Summary(x =>
     {
        x.Summary = "Get All Categories";
        x.Description = "Fetches a list of all categories.";
     });
 }

 public override async Task HandleAsync(CancellationToken c)
 {
    var categories = await db.Categories
        .Select(category => new CategoryDto(category.Name , category.Description))
        .ToListAsync(c);

    await SendAsync(categories, StatusCodes.Status200OK, c);
 }

}

I am facing multiple problems here.

  1. The main challenge in testing FastEndpoints lies in correctly mocking DbSet and its asynchronous methods like ToListAsync, as they rely on IQueryable and IAsyncEnumerable.

  2. Additionally, mocking base class methods like SendAsync is difficult since they are not virtual, requiring the use of Received() to verify method calls instead of direct mocking.

Below are my Test case:

public class GetAllEndpointTestsNSubstitute{

 private readonly GetAllEndpoint _endpoint;
 private readonly AppDbContext _mockDbContext;
 private readonly DbSet<Category> _mockCategoriesDbSet;
 private readonly List<Category> _categories;

public GetAllEndpointTestsNSubstitute()
{
    _categories = new List<Category>
        {
            new Category { Name = "Category1", Description = "Description1" },
            new Category { Name = "Category2", Description = "Description2" }
        };

    // Mocking IQueryable<Category> for DbSet<Category>
    var categoryQueryable = _categories.AsQueryable();

    _mockCategoriesDbSet = Substitute.For<DbSet<Category>, IQueryable<Category>>();
    ((IQueryable<Category>)_mockCategoriesDbSet).Provider.Returns(categoryQueryable.Provider);
    ((IQueryable<Category>)_mockCategoriesDbSet).Expression.Returns(categoryQueryable.Expression);
    ((IQueryable<Category>)_mockCategoriesDbSet).ElementType.Returns(categoryQueryable.ElementType);
    ((IQueryable<Category>)_mockCategoriesDbSet).GetEnumerator().Returns(categoryQueryable.GetEnumerator());

    // Mock the ToListAsync method to return the list of categories
    _mockCategoriesDbSet.ToListAsync(Arg.Any<CancellationToken>())
        .Returns(callInfo => Task.FromResult(_categories.ToList()));

    // Mock the DbContext to return the mock DbSet<Category>
    _mockDbContext = Substitute.For<AppDbContext>();
    _mockDbContext.Categories.Returns(_mockCategoriesDbSet);

    // Instantiate the endpoint with the mocked DbContext
    _endpoint = new GetAllEndpoint(_mockDbContext);
}

[Fact]
public async Task HandleAsync_ShouldReturnCategories()
{
    // Arrange
    var cancellationToken = new CancellationToken();

    // Act
    await _endpoint.HandleAsync(cancellationToken);

    // Assert
    var expectedCategories = _categories
        .Select(c => new CategoryDto(c.Name, c.Description))
        .ToList();

    // Check that the count is correct
    Assert.Equal(2, expectedCategories.Count);

    // Check that the expected category names match
    Assert.Equal("Category1", expectedCategories[0].Name);
    Assert.Equal("Category2", expectedCategories[1].Name);
}

}

Below is the error

NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException : Could not find a call to return from.

Share Improve this question asked Feb 4 at 16:26 Hassan MunirHassan Munir 384 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

you can't just instantiate an endpoint class yourself. as mentioned in the documentation, you have to instantiate endpoints like below for the purpose of unit testing:

var ep = Factory.Create<GetAllEndpoint>(mockDbContext);

then you call await ep.HandleAsync() and assert on the ep.Response property.

also, you shouldn't share the ep instance with multiple tests. it should be instantiated per test.

as for mocking the ef core DbContext, that is something you really shouldn't be doing. either use in-memory or sqlite provider or introduce the repository pattern so you can easily mock the repo methods. see the microsoft guidelines here.

in general, doing unit tests for stuff that involves data access is less valuable than integration testing. consider doing integration testing with something like testcontainers and a real database. here's a mongodb example.

发布评论

评论列表(0)

  1. 暂无评论