Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
{ name: "Testcontainers.MariaDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Milvus", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Minio", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Mockaco", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MongoDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MsSql", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.MySql", runs-on: "ubuntu-22.04" },
Expand Down
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Xunit.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.XunitV3.Tests", "tests\Testcontainers.XunitV3.Tests\Testcontainers.XunitV3.Tests.csproj", "{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mockaco", "src\Testcontainers.Mockaco\Testcontainers.Mockaco.csproj", "{B723CF67-8A90-428C-BD6F-885E1DA2F5F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mockaco.Tests", "tests\Testcontainers.Mockaco.Tests\Testcontainers.Mockaco.Tests.csproj", "{E0DEA5DB-4985-4EFB-8824-F561C690F3CF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -751,6 +755,14 @@ Global
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.Build.0 = Release|Any CPU
{B723CF67-8A90-428C-BD6F-885E1DA2F5F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B723CF67-8A90-428C-BD6F-885E1DA2F5F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B723CF67-8A90-428C-BD6F-885E1DA2F5F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B723CF67-8A90-428C-BD6F-885E1DA2F5F4}.Release|Any CPU.Build.0 = Release|Any CPU
{E0DEA5DB-4985-4EFB-8824-F561C690F3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0DEA5DB-4985-4EFB-8824-F561C690F3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0DEA5DB-4985-4EFB-8824-F561C690F3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0DEA5DB-4985-4EFB-8824-F561C690F3CF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -878,5 +890,7 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E901DF14-6F05-4FC2-825A-3055FAD33561} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{B723CF67-8A90-428C-BD6F-885E1DA2F5F4} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{E0DEA5DB-4985-4EFB-8824-F561C690F3CF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ await moduleNameContainer.StartAsync();
| MariaDB | `mariadb:10.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.MariaDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MariaDb) |
| Milvus | `milvusdb/milvus:v2.3.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.Milvus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Milvus) |
| MinIO | `minio/minio:RELEASE.2023-01-31T02-24-19Z` | [NuGet](https://www.nuget.org/packages/Testcontainers.Minio) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Minio) |
| Mockaco | `natenho/mockaco:1.9.14` | [NuGet](https://www.nuget.org/packages/Testcontainers.Mockaco) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Mockaco) |
| MongoDB | `mongo:6.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDb) |
| MySQL | `mysql:8.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MySql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MySql) |
| NATS | `nats:2.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Nats) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Nats) |
Expand Down
116 changes: 116 additions & 0 deletions docs/modules/mockaco.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Mockaco

[Mockaco](https://natenho.github.io/Mockaco/) is a HTTP-based API mock server for .NET Core applications. It's designed to be simple, lightweight, and easy to use for testing and development purposes.

## Prerequisites

Before using Mockaco, you need to create mock templates (JSON files) that define the API endpoints and their responses. These templates should be placed in a folder that will be mounted to the container.

Create a template file (e.g., `ping-pong.json`) in your templates folder:

```json title="./templates/ping-pong.json"
{
"request": {
"method": "GET",
"route": "ping"
},
"response": {
"status": "OK",
"body": {
"response": "pong"
}
}
}
```

For more information about creating templates, see the [official Mockaco documentation](https://natenho.github.io/Mockaco/docs/quick-start/create-mock).

## Installation

Add the following dependency to your project file:

```shell title="NuGet"
dotnet add package Testcontainers.Mockaco
```

You can start a Mockaco container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.

**Note:** The `WithTemplatesPath()` method specifies the local folder containing your JSON template files, which will be mounted to the container's `/app/Mocks` directory.

=== "Test class"
```csharp
public sealed class MockacoContainerTest : IAsyncLifetime
{
private readonly MockacoContainer _mockacoContainer = new MockacoBuilder()
.WithTemplatesPath("./templates") // Local folder with JSON templates
.Build();

public async ValueTask InitializeAsync()
{
await _mockacoContainer.StartAsync()
.ConfigureAwait(false);
}

public async ValueTask DisposeAsync()
{
await _mockacoContainer.DisposeAsync()
.ConfigureAwait(false);
}
}
```

Set up and call a mock endpoint:

=== "Mock endpoint setup"
```csharp
[Fact]
public async Task SetupAndCallMockEndpoint()
{
// Given
using var httpClient = new HttpClient();
var baseUrl = new Uri(_mockacoContainer.GetBaseAddress());

// When - Call the mock endpoint
var response = await httpClient.GetAsync(new Uri(baseUrl, "/ping"));

// Then
Assert.True(response.IsSuccessStatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("pong", content);
}
```

Verify API calls using the verification endpoint:

=== "Verify API calls"
```csharp
[Fact]
public async Task VerifyApiCallsWithVerificationEndpoint()
{
// Given
using var httpClient = new HttpClient();
var baseUrl = new Uri(_mockacoContainer.GetBaseAddress());

// When - Call an endpoint and then verify
await httpClient.GetAsync(new Uri(baseUrl, "/ping"));
var verification = await _mockacoContainer.GetVerifyAsync("/ping");

// Then
Assert.NotNull(verification);
Assert.Equal("/ping", verification.Route);
Assert.Contains("pong", verification.Body);
}
```

The test example uses the following NuGet dependencies:

=== "Package References"
```xml
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="xunit.runner.visualstudio"/>
```

To execute the tests, use the command `dotnet test` from a terminal.

--8<-- "docs/modules/_call_out_test_projects.txt"
1 change: 1 addition & 0 deletions src/Testcontainers.Mockaco/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
102 changes: 102 additions & 0 deletions src/Testcontainers.Mockaco/MockacoBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.IO;
using System.Net.Http;

namespace Testcontainers.Mockaco;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
public sealed class MockacoBuilder : ContainerBuilder<MockacoBuilder, MockacoContainer, MockacoConfiguration>
{
/// <summary>
/// The default Docker image used for Mockaco.
/// </summary>
public const string MockacoImage = "natenho/mockaco:1.9.14";

/// <summary>
/// The default port exposed by the Mockaco container.
/// </summary>
public const ushort MockacoPort = 5000;

/// <summary>
/// Initializes a new instance of the <see cref="MockacoBuilder" /> class
/// with the default <see cref="MockacoConfiguration" />.
/// </summary>
public MockacoBuilder() : this(new MockacoConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="MockacoBuilder" /> class
/// with the provided <see cref="MockacoConfiguration" />.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private MockacoBuilder(MockacoConfiguration resourceConfiguration) : base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <summary>
/// Sets the path to the templates directory for the Mockaco container.
/// </summary>
/// <param name="templatesPath">The absolute path to the templates directory.</param>
/// <returns>The updated <see cref="MockacoBuilder" /> instance.</returns>
public MockacoBuilder WithTemplatesPath(string templatesPath)
{
return Merge(DockerResourceConfiguration, new MockacoConfiguration(templatesPath: templatesPath))
.WithBindMount(Path.GetFullPath(templatesPath), "/app/Mocks", AccessMode.ReadWrite);
}


/// <inheritdoc />
public override MockacoContainer Build()
{
Validate();
return new MockacoContainer(DockerResourceConfiguration);
}

/// <inheritdoc />
protected override MockacoConfiguration DockerResourceConfiguration { get; }

/// <inheritdoc />
protected override MockacoBuilder Init()
{
return base.Init()
.WithImage(MockacoImage)
.WithPortBinding(MockacoPort, true)
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(request => request
.WithMethod(HttpMethod.Get)
.ForPort(MockacoPort)
.ForPath("_mockaco/ready")
.WithContent(() => new StringContent("Healthy"))));
}

/// <inheritdoc />
protected override void Validate()
{
base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.TemplatesPath,
nameof(DockerResourceConfiguration.TemplatesPath))
.NotNull()
.NotEmpty();
}

/// <inheritdoc />
protected override MockacoBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new MockacoConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override MockacoBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new MockacoConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override MockacoBuilder Merge(MockacoConfiguration oldValue, MockacoConfiguration newValue)
{
return new MockacoBuilder(new MockacoConfiguration(oldValue, newValue));
}
}
59 changes: 59 additions & 0 deletions src/Testcontainers.Mockaco/MockacoConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace Testcontainers.Mockaco;

public class MockacoConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="MockacoConfiguration" /> class.
/// </summary>
/// <param name="templatesPath">The path which contains mock templates.</param>
public MockacoConfiguration(string templatesPath = null)
{
TemplatesPath = templatesPath ?? "/Mocks/Templates";
}

/// <summary>
/// Initializes a new instance of the <see cref="MockacoConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public MockacoConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="MockacoConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public MockacoConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="MockacoConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public MockacoConfiguration(MockacoConfiguration resourceConfiguration)
: this(new MockacoConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="MockacoConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public MockacoConfiguration(MockacoConfiguration oldValue, MockacoConfiguration newValue)
: base(oldValue, newValue)
{
TemplatesPath = BuildConfiguration.Combine(oldValue?.TemplatesPath, newValue?.TemplatesPath);
}

/// <summary>
/// Gets the path to the mock's template files.
/// </summary>
public string TemplatesPath { get; }
}
Loading