I recently started using TestServer class to self-host and bootstrap an Aspnet Core API to run Integration Tests without a dedicated running environment.
I love the way it works and using custom environment name, I decided to control the way the EF Context is created, switching from SQL Server to In-Memory database.
Problem is that in order to seed the data necessary to run the tests via API requests, would be very expensive for both coding and running time.
My idea is to create a class or a simple framework to seed the data necessary by each test, but to do so I need to use the same in-memory database which is initialized with the API stack by the TestServer.
How is it possible to do so?
In the first place is important to understand that for better testing in replacement to a relational database such SQL Server, In Memory database is not ideal.
Among the various limitations, it does not support foreign key constraints. A better way to use an in-memory database is to use SQLite In-Memory mode.
Here is the code I used to setup the TestServer, Seed the Data and register the DB Context for the Dependency Injection:
TestServer
public class ApiClient {
private HttpClient _client;
public ApiClient()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseEnvironment("Test");
webHostBuilder.UseStartup<Startup>();
var server = new TestServer(webHostBuilder);
_client = server.CreateClient();
}
public async Task<HttpResponseMessage> PostAsync<T>(string url, T entity)
{
var content = new StringContent(JsonConvert.SerializeObject(entity), Encoding.UTF8, "application/json");
return await _client.PostAsync(url, content);
}
public async Task<T> GetAsync<T>(string url)
{
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseString);
}
}
Data Seeding (Helper class)
public class TestDataConfiguration
{
public static IMyContext GetContex()
{
var serviceCollection = new ServiceCollection();
IocConfig.RegisterContext(serviceCollection, "", null);
var serviceProvider = serviceCollection.BuildServiceProvider();
return serviceProvider.GetService<IMyContext>();
}
}
Data Seeding (Test class)
[TestInitialize]
public void TestInitialize()
{
_client = new ApiClient();
var context = TestDataConfiguration.GetContex();
var category = new Category
{
Active = true,
Description = "D",
Name = "N"
};
context.Categories.Add(category);
context.SaveChanges();
var transaction = new Transaction
{
CategoryId = category.Id,
Credit = 1,
Description = "A",
Recorded = DateTime.Now
};
context.Transactions.Add(transaction);
context.SaveChanges();
}
DB Context registration (In IocConfig.cs)
public static void RegisterContext(IServiceCollection services, string connectionString, IHostingEnvironment hostingEnvironment)
{
if (connectionString == null)
throw new ArgumentNullException(nameof(connectionString));
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddDbContext<MyContext>(options =>
{
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");
connection.Open();
options.UseSqlite(connection);
options.UseLoggerFactory(MyLoggerFactory);
}
else
{
options.UseSqlServer(connectionString);
options.UseLoggerFactory(MyLoggerFactory);
}
});
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
services.AddSingleton<IMyContext>(service =>
{
var context = service.GetService<MyContext>();
context.Database.EnsureCreated();
return context;
});
} else {
services.AddTransient<IMyContext>(service => service.GetService<MyContext>());
}
}
The key is the URI file string used to create the SQLite connection:
var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");