I am trying to create integration tests for my microservices similar to Spotify's approach.
I am still working on how to spin up and seed the database. Currently I have a .NET Core 2.0 project with FluentDocker v2.2.15 and DbUp 4.1.0.
I use FluentDocker to call DockerCompose and start my services, including the SQL Server container
var hosts = new Hosts().Discover();
var dockerHost = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default");
if (dockerHost == null)
{
return;
}
var composeFile = Args["composeFile"];
var result = dockerHost.Host.ComposeUp(composeFile: composeFile);
and then I use DbUp to run my scripts and seed the database.
var connectionString = Args["connectionString"];
var scriptsPath = Args["scriptsPath"];
EnsureDatabase.For.SqlDatabase(connectionString);
var upgradeEngine = DeployChanges.To.SqlDatabase(connectionString).WithScriptsFromFileSystem(scriptsPath).Build();
var result = upgradeEngine.PerformUpgrade();
I can run this successfully when I give SQL Server enough time to start, for example, when debugging. However, if I run this at full speed, then DbUp tries to connect to SQL Server when it isn't ready yet.
FluentDocker has a WaitForPort
method but it doesn't seem to work with DockerCompose API.
I would like to know if there is a way to wait for SQL Server's port 1433 to be responsive before running the scripts (excluding non-deterministic tactics such as await Task.Delay
) or if there are alternative libraries that allow me to have this kind of control.
Thanks
You can use WaitForPort
, WaitForProcess
, WaitForHttp
, or custom lambda Wait
functions on compose in FluentDocker v2.6.2. For example:
Given the docker-compose file:
version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes:
db_data:
The file specifies that wordpress depends on db and thus the db is started first and then the wordpress container is instantiated. However to ensure that within
the using
clause that the wordpress web is running you need to use HTTP to determine just that. You can use the Fluent API to instantiate and wait for the service to start like this.
var file = Path.Combine(Directory.GetCurrentDirectory(),
(TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml");
using (new Builder()
.UseContainer()
.UseCompose()
.FromFile(file)
.RemoveOrphans()
.Wait("wordpress", (service, cnt) => {
if (cnt > 60) throw new FluentDockerException("Failed to wait for wordpress service");
var res = HttpExtensions.DoRequest("http://localhost:8000/wp-admin/install.php").Result;
return (res.Code == HttpStatusCode.OK &&
res.Body.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1) ? 0 : 500;
})
.Build().Start())
{
// Since we have waited - this shall now always work.
var installPage = await "http://localhost:8000/wp-admin/install.php".Wget();
Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1);
}
(The above sample is a more cumbersome WaitForHttp
but using custom lambda to illustrate the point).
In this way you could even use a db connection and query a table before continuation. Return values above zero is the time to wait until next time to test. Zero and below will end the wait successfully. An exception will terminate the wait (and fail).
The example above uses the FluentAPI syntax, but you can add manually a hook onto compose container and use the extensions by yourself.
Cheers, Mario