.Net TestContainers and Integration Tests

105 Views Asked by At

This has been driving me crazy, so any help would be much appreciated.

I have the following Azure-pipelines.yml CI pipeline:

  trigger:
- master

pool:
  vmImage: ubuntu-latest

variables:
  buildConfiguration: 'Release'
  BuildParameters.RestoreBuildProjects: '**/*.csproj'
  BuildParameters.TestProjects: |
     $(BuildParameters.RestoreBuildProjects) 
     !**/*IntegrationTests*.csproj
     !**/*FunctionalTests*.csproj

jobs:
- job: BuildAndTest
  displayName: Build and Test

  steps:
  - task: DockerCompose@0
    displayName: "Run a Docker Compose command"
    inputs:
      dockerComposeFile: "docker-compose-integration-tests.yml"
      dockerComposeCommand: "up --abort-on-container-exit" 
    env:
      CONTAINER_NAME: 'bookywooks_orderapi_integrationtests'  

  - task: DotNetCoreCLI@2
    displayName: Restore Nuget Packages 
    inputs:
       command: restore
       projects: $(BuildParameters.RestoreBuildProjects)
       configuration: $(buildConfiguration)  

  - task: DotNetCoreCLI@2
    displayName: 'dotnet build $(buildConfiguration)'
    inputs:
      command: 'build'
      projects: '**/BookyWooks.sln'
      arguments: '--configuration $(buildConfiguration)'

  - script: docker --version
    displayName: 'Print Docker Version'

  - task: DotNetCoreCLI@2
    displayName: Run Tests
    inputs:
      command: test
      projects: $(BuildParameters.TestProjects) 
      arguments: --configuration $(buildConfiguration) --no-build

The "Run a Docker Compose command" step allows my integration tests to run successfully. So this step passes. Here is the docker-compose-integration-tests.yml for that:

version: '3.4'

services:
  # Service for running integration tests
  bookywooks_orderapi_integrationtests:
    image: ${DOCKER_REGISTRY-}bookywooksorderapiintegrationtests
    build:
      context: .
      dockerfile: BookWooks.OrderApi/src/BookWooks.OrderApi.Web/Dockerfile
      target: integrationtest  # Specify the build target
    depends_on:
      - integrationtestsdatabase  # Depends on the integration tests database service
      - rabbitmq  # Depends on the RabbitMQ service
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - "ConnectionStrings__DefaultConnection=Server=integrationtestsdatabase;Database=BookyWooks.OrderDbIntegrationTests;User=sa;Password=Your_password123; MultipleActiveResultSets=true; TrustServerCertificate=true;"  # Database connection string
      - RabbitMQConfiguration__Config__HostName=${BOOKYWOOKS_RABBITMQ_HOSTNAME:-rabbitmq}  # RabbitMQ host name
      - RabbitMQConfiguration__Config__UserName=${BOOKYWOOKS_RABBITMQ_USERNAME:-guest}  # RabbitMQ user name
      - RabbitMQConfiguration__Config__Password=${BOOKYWOOKS_RABBITMQ_PASSWORD:-guest}  # RabbitMQ password
    volumes:
      - ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests  # Mount volume for test results
    entrypoint:
      - dotnet  # Entry point to execute dotnet commands
      - test  # Command to run tests
      - --logger  # Specify logger options
      - trx;LogFileName=/tests/booky-wooks-integration-test-results.xml  # Use trx logger format and specify log file path

  # Service for integration tests database (SQL Server)
  integrationtestsdatabase:
    image: "mcr.microsoft.com/mssql/server:2022-latest"  # Use SQL Server 2022 image
    environment:
       - "SA_PASSWORD=Your_password123"  # Set SA password
       - "ACCEPT_EULA=Y"  # Accept End-User License Agreement
    ports:
    -   "5433:1433"  # Map container port 1433 to host port 5433

  # Service for RabbitMQ
  rabbitmq:
    ports:
      - "15672:15672"  # Map RabbitMQ management port
      - "5672:5672"    # Map RabbitMQ port for message communication
    image: rabbitmq:3-management-alpine  # Use RabbitMQ 3 with management plugin

However, I am trying to implement similar with Test Containers. That is unfortunately causing the Run Tests task of my azure-pipeline to fail. This is due to the following errors relating to connecting to RabbitMQ and SQL Server.:

My Rabbit MQ error is:
2024-03-12T19:14:08.6922155Z Error creating RabbitMQ connection: None of the specified endpoints were reachable

And Sql Server error is:

2024-03-12T19:14:08.9058528Z    Microsoft.Data.SqlClient.SqlException : A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 35 - An internal exception was caught)

Here is my Application Test Factory setup:

    public class OrderApiApplicationFactory<TEntryPoint> : WebApplicationFactory<Program>, IAsyncLifetime where TEntryPoint : Program // : WebApplicationFactory<Program>, IAsyncLifetime
{

private const string Database = "master";
private const string Username = "sa";
private const string Password = "yourStrong(!)Password";
private string _connectionString;
private const ushort MsSqlPort = 1433;
private const ushort RabbitMqPort = 5672; 
private readonly MsSqlContainer _mssqlContainer;
private readonly IContainer _rabbitMqContainer;

    public HttpClient HttpClient { get; private set; } = default!;

    private const string RabbitMqUsername = "guest";
    private const string RabbitMqPassword = "guest";

    public OrderApiApplicationFactory()
    {
   

     _mssqlContainer = new MsSqlBuilder()
    .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
    // Remove static port binding and let Docker assign random ports
    .WithPortBinding(0, MsSqlPort)
    .WithEnvironment("ACCEPT_EULA", "Y") 
    .WithEnvironment("SA_PASSWORD", Password)
    .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlPort))
    .Build();

     _rabbitMqContainer = new RabbitMqBuilder()
    .WithImage("rabbitmq:3-management-alpine") 
    .WithPortBinding(5672, RabbitMqPort) 
    .WithEnvironment("RABBITMQ_DEFAULT_USER", RabbitMqUsername)
    .WithEnvironment("RABBITMQ_DEFAULT_PASS", RabbitMqPassword)
    .WithWaitStrategy(Wait.ForUnixContainer()
        .UntilPortIsAvailable(RabbitMqPort) 
        .UntilPortIsAvailable(15672)) 
    .Build();
    }

    public async Task InitializeAsync()
    {
        await _mssqlContainer.StartAsync();
        await _rabbitMqContainer.StartAsync();
       
        GetDatabaseConnectionString();

        HttpClient = CreateClient();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration(configurationBuilder =>
        {
            var configuration = new ConfigurationBuilder().AddJsonFile("testcontainersappsettings.json")
       .AddInMemoryCollection(new Dictionary<string, string>
       {
           ["ConnectionStrings:DefaultConnection"] =  _connectionString,
           ["RabbitMQConfiguration:Config:HostName"] = _rabbitMqContainer.Hostname
       })
       .AddEnvironmentVariables()
       .Build();

            configurationBuilder.AddConfiguration(configuration);
        });

        builder.ConfigureServices(services =>
        {
            RemoveDbContextOptions<BookyWooksOrderDbContext>(services);
            RemoveDbContextOptions<IntegrationEventLogDbContext>(services);

            AddDbContext<BookyWooksOrderDbContext>(services);
            AddDbContext<IntegrationEventLogDbContext>(services);
        });
    }

    private void RemoveDbContextOptions<T>(IServiceCollection services) where T : DbContext
    {
        var descriptor = services.SingleOrDefault(
            d => d.ServiceType == typeof(DbContextOptions<T>));

        if (descriptor != null)
            services.Remove(descriptor);
    }

    private void AddDbContext<T>(IServiceCollection services) where T : DbContext
    {
        services.AddDbContext<T>(options =>
        {
            options.UseSqlServer(_connectionString,
                builder => builder.MigrationsAssembly(typeof(T).Assembly.FullName));
        });
    }
    private string GetDatabaseConnectionString()
    {
        _connectionString = _mssqlContainer.GetConnectionString(); // $"Server={_mssqlContainer.Hostname},{_mssqlContainer.GetMappedPublicPort(MsSqlPort)};Database={Database};User Id={Username};Password={Password};TrustServerCertificate=True";
        return _connectionString;
    }

    public new async Task DisposeAsync()
    {
        await _mssqlContainer.DisposeAsync();
        await _rabbitMqContainer.DisposeAsync();
    }
}

I believe they are set up similarly. Please note my test containers set up is working when I run the tests locally with docker but not in my azure-pipeline. Would anyone know why?

0

There are 0 best solutions below