AspNetCore.Testing.MadeEasy provides as implementation of DbAsyncQueryProvider
that can be used when testing a component that uses async queries with EntityFrameworkCore. Also it's provides utility to run database through docker for testing and dispose them automatically once container is being stopped.
It also comes with some helpful extensions and mock functionality to easy the testing through Moq
Unit test cases set up is quite inspired from EntityFramework.Testing. But for Ef core.
It supports mock of following opeartions:
Find
FindAsync
Add
AddAsync
Attach
Remove
AddRange
AddRangeAsync
AttachRange
RemoveRange
AddRange
public class PersonController : ControllerBase
{
private readonly DatabaseContext context;
public PersonController(DatabaseContext database)
{
this.context = database;
}
[HttpGet]
public IEnumerable<Person> Get()
{
return context.Person
.Where(x => x.Status == Status.Active).ToArray();
}
}
[Fact]
public void Get_should_return_valid_persons()
{
var data = new List<Person>{
new Person { Name = "AAA", Status = Status.Active },
new Person { Name = "BBB", Status = Status.Active },
new Person { Name = "CCC", Status = Status.Active },
new Person { Name = "DDD", Status = Status.Inactive },
};
var mockContext = new Mock<DatabaseContext>();
// using lib
var dbset = MockDb.CreateDbSet<Person>(data);
mockContext.Setup(x => x.Person).Returns(dbset.Object);
var controller = new PersonController(mockContext.Object);
var result = controller.Get();
Assert.Equal(3, result.Count);
}
Under the hood, it is using Testcontainer to run the docker container. This library provides some support functions to quickly run the container and set up one's test cases.
- Configure
appsettings.Testing.json
{"AspNetCore.Testing.MadeEasy": {
"UseExternaldb": false,
"ConnectionString": "Server=localhost;database=example;User Id=postgres;password=welcome;port=5432;",
"DockerDb": {
"Image": "kartoza/postgis",
"ContainerName": "example_postgris",
"HostPort": 5432,
"ContainerPort": 5432,
"EnviromentVariables": {
"POSTGRES_USER": "postgres",
"POSTGRES_PASSWORD": "welcome",
"POSTGRES_DB": "example"
}
}
}
}
- Get database container and start it
var manager = DatabaseManager();
await manager.SpinContainer();
- One can run a test case by inheriting TestBase and overriding ConfigureServices to inject any service for the test case. Although it is better to not run integration test case in parallel as they are sharing resources like database. Check test case framework you are using for running the test cases in order.
public class IntegrationTest : TestBase<DatabaseContext, Program>
{
[Fact]
public async Task Person_get_api_should_return_result()
=> await RunTest(
populatedb: async ctx =>
{
var person = PersonFactory.GetPerson();
await ctx.Person!.AddAsync(person);
await ctx.SaveChangesAsync();
},
addAuth: async client =>
{
/*Add auth headers to the client*/
},
test: async client =>
{
var response = await client.GetAsync("/person");
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var data = content.RootElement.EnumerateArray().ToList();
Assert.Single(data);
},
validateDb: async ctx =>
{
/*It can be used to validate data exist or not in case or update/insert and for some other cases */
},
cleanDb: async ctx =>
{
ctx.Person.Clear();
await ctx.SaveChangesAsync();
});
protected override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddEntityFrameworkNpgsql().AddDbContext<DatabaseContext>(
opt =>
{
opt.UseNpgsql(DatabaseManager.ConnectionString, o => o.UseNetTopologySuite());
});
}
}
- Finally stop the database container
await manager.StopContainer();
For a detailed example, you can refer to this link
- Extension for logger
var logger = new Mock<ILogger<MyClass>>();
_ = new MyClass(logger.Object);
logger.VerifyLogging("Test information", LogLevel.Information);
logger.VerifyLogging("Some error", LogLevel.Error, Times.Exactly(2));
- Create mock HttpClientFactory
var (factory, httpMessageHandler) = MockHttpClient.GetMockedNamedHttpClientFactory(
baseUrl: "https://localhost.xyz",
subUrl: "/data",
response: @"{""name"":""Alex""}",
responseStatusCode: HttpStatusCode.OK,
httpMethod: HttpMethod.Get);
_ = new MyClass(factory.Object);
handler.VerifyAll();