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.
The main challenge in testing FastEndpoints lies in correctly mocking DbSet and its asynchronous methods like ToListAsync, as they rely on IQueryable and IAsyncEnumerable.
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.
The main challenge in testing FastEndpoints lies in correctly mocking DbSet and its asynchronous methods like ToListAsync, as they rely on IQueryable and IAsyncEnumerable.
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 badges1 Answer
Reset to default 0you 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.