4

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

peflorencio
  • 2,284
  • 2
  • 32
  • 40
  • 1
    Waiting for port 1433 isn't necessarily sufficient -- at this point SQL Server may still be in the process of starting up databases (although all system databases should be up, so `CREATE DATABASE` for a new one should work regardless). Even so, a better approach is to wait for a trivial query to succeed (like `SELECT TOP(0) * FROM master.sys.tables`). In "native" docker-compose this can be done with `depends_on` an a healthcheck. For FluentDocker, no clue. – Jeroen Mostert Jun 15 '18 at 14:37
  • You should be able to do ```depends_on``` on a compose file using FluentDocker since it will use ```docker-compose``` native binary to drive the compose file. – Mario Toffia Jun 27 '18 at 14:18

1 Answers1

2

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

Mario Toffia
  • 510
  • 5
  • 16