Skip to content

Commit 2e3f43a

Browse files
refactor: improve test structure and performance by utilizing fixtures (#157)
Signed-off-by: SebastienDegodez <[email protected]>
1 parent 84031e9 commit 2e3f43a

10 files changed

+416
-321
lines changed

src/Microcks.Testcontainers/MicrocksBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public MicrocksBuilder WithMainRemoteArtifacts(params string[] urls)
192192
_mainRemoteArtifacts = new List<RemoteArtifact>(urls.Length);
193193
}
194194
_mainRemoteArtifacts.AddRange(urls.Select(url => new RemoteArtifact(url, null)));
195-
195+
196196
return this;
197197
}
198198

tests/Microcks.Testcontainers.Tests/Async/Kafka/MicrocksAsyncKafkaFunctionalityTest.cs

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,56 +24,34 @@
2424
using Microcks.Testcontainers.Model;
2525
using Microsoft.Extensions.Logging;
2626
using Testcontainers.Kafka;
27+
using DotNet.Testcontainers.Networks;
2728

2829
namespace Microcks.Testcontainers.Tests.Async.Kafka;
2930

3031
[Collection(nameof(KafkaCollection))]
31-
public sealed class MicrocksAsyncKafkaFunctionalityTest : IAsyncLifetime
32+
public sealed class MicrocksAsyncKafkaFunctionalityTest
33+
: IClassFixture<MicrocksAsyncKafkaFunctionalityTest.KafkaFixture>
3234
{
3335
/// <summary>
3436
/// Image name for the Microcks container.
3537
/// </summary>
3638
private const string MicrocksImage = "quay.io/microcks/microcks-uber:1.10.0";
3739

38-
private MicrocksContainerEnsemble _microcksContainerEnsemble;
40+
private readonly KafkaFixture _fixture;
3941

40-
private KafkaContainer _kafkaContainer;
41-
42-
public async ValueTask DisposeAsync()
43-
{
44-
await this._microcksContainerEnsemble.DisposeAsync();
45-
await this._kafkaContainer.DisposeAsync();
46-
}
47-
48-
public async ValueTask InitializeAsync()
42+
public MicrocksAsyncKafkaFunctionalityTest(KafkaFixture fixture)
4943
{
50-
var network = new NetworkBuilder().Build();
51-
52-
this._kafkaContainer = new KafkaBuilder()
53-
.WithImage("confluentinc/cp-kafka:7.8.0")
54-
.WithNetwork(network)
55-
.WithNetworkAliases("kafka")
56-
.WithListener("kafka:19092")
57-
.Build();
58-
59-
// Start the Kafka container
60-
await this._kafkaContainer.StartAsync();
61-
62-
this._microcksContainerEnsemble = new MicrocksContainerEnsemble(network, MicrocksImage)
63-
.WithMainArtifacts("pastry-orders-asyncapi.yml")
64-
.WithKafkaConnection(new KafkaConnection($"kafka:19092"));
65-
66-
await this._microcksContainerEnsemble.StartAsync();
44+
_fixture = fixture;
6745
}
6846

6947
[Fact]
7048
public void ShouldReceivedKafkaMessageWhenMessageIsEmitted()
7149
{
7250
const string expectedMessage = "{\"id\":\"4dab240d-7847-4e25-8ef3-1530687650c8\",\"customerId\":\"fe1088b3-9f30-4dc1-a93d-7b74f0a072b9\",\"status\":\"VALIDATED\",\"productQuantities\":[{\"quantity\":2,\"pastryName\":\"Croissant\"},{\"quantity\":1,\"pastryName\":\"Millefeuille\"}]}";
73-
var kafkaTopic = this._microcksContainerEnsemble.AsyncMinionContainer
51+
var kafkaTopic = _fixture.MicrocksContainerEnsemble.AsyncMinionContainer
7452
.GetKafkaMockTopic("Pastry orders API", "0.1.0", "SUBSCRIBE pastry/orders");
7553

76-
var bootstrapServers = this._kafkaContainer.GetBootstrapAddress()
54+
var bootstrapServers = _fixture.KafkaContainer.GetBootstrapAddress()
7755
.Replace("PLAINTEXT://", "", StringComparison.OrdinalIgnoreCase);
7856

7957
// Initialize Kafka consumer to receive message
@@ -127,7 +105,7 @@ public async Task ShouldReturnsCorrectStatusContractWhenGoodMessageIsEmitted()
127105
// Init Kafka producer to send a message
128106
var producerConfig = new ProducerConfig
129107
{
130-
BootstrapServers = this._kafkaContainer.GetBootstrapAddress()
108+
BootstrapServers = _fixture.KafkaContainer.GetBootstrapAddress()
131109
.Replace("PLAINTEXT://", "", StringComparison.OrdinalIgnoreCase),
132110
ClientId = $"test-client-{DateTime.Now.Ticks}",
133111
};
@@ -137,15 +115,15 @@ public async Task ShouldReturnsCorrectStatusContractWhenGoodMessageIsEmitted()
137115
.SetValueSerializer(Serializers.Utf8)
138116
.SetErrorHandler((_, e) =>
139117
{
140-
this._kafkaContainer.Logger.LogError("Error: {Reason}", e.Reason);
118+
_fixture.KafkaContainer.Logger.LogError("Error: {Reason}", e.Reason);
141119
})
142120
.SetLogHandler((_, logMessage) =>
143121
{
144-
this._kafkaContainer.Logger.LogInformation("{Name} sending {Message}", logMessage.Name, logMessage.Message);
122+
_fixture.KafkaContainer.Logger.LogInformation("{Name} sending {Message}", logMessage.Name, logMessage.Message);
145123
})
146124
.Build();
147125

148-
var taskTestResult = this._microcksContainerEnsemble
126+
var taskTestResult = _fixture.MicrocksContainerEnsemble
149127
.MicrocksContainer
150128
.TestEndpointAsync(testRequest, TestContext.Current.CancellationToken);
151129

@@ -197,7 +175,7 @@ public async Task ShouldReturnsCorrectStatusContractWhenBadMessageIsEmitted()
197175
// Init Kafka producer to send a message
198176
var producerConfig = new ProducerConfig
199177
{
200-
BootstrapServers = this._kafkaContainer.GetBootstrapAddress()
178+
BootstrapServers = _fixture.KafkaContainer.GetBootstrapAddress()
201179
.Replace("PLAINTEXT://", "", StringComparison.OrdinalIgnoreCase),
202180
ClientId = $"test-client-{DateTime.Now.Ticks}",
203181
};
@@ -207,15 +185,15 @@ public async Task ShouldReturnsCorrectStatusContractWhenBadMessageIsEmitted()
207185
.SetValueSerializer(Serializers.Utf8)
208186
.SetErrorHandler((_, e) =>
209187
{
210-
this._kafkaContainer.Logger.LogError("Error: {Reason}", e.Reason);
188+
_fixture.KafkaContainer.Logger.LogError("Error: {Reason}", e.Reason);
211189
})
212190
.SetLogHandler((_, logMessage) =>
213191
{
214-
this._kafkaContainer.Logger.LogInformation("{Name} sending {Message}", logMessage.Name, logMessage.Message);
192+
_fixture.KafkaContainer.Logger.LogInformation("{Name} sending {Message}", logMessage.Name, logMessage.Message);
215193
})
216194
.Build();
217195

218-
var taskTestResult = this._microcksContainerEnsemble
196+
var taskTestResult = _fixture.MicrocksContainerEnsemble
219197
.MicrocksContainer
220198
.TestEndpointAsync(testRequest, TestContext.Current.CancellationToken);
221199
await Task.Delay(750, TestContext.Current.CancellationToken);
@@ -249,7 +227,7 @@ public async Task ShouldReturnsCorrectStatusContractWhenBadMessageIsEmitted()
249227
Assert.Contains("object has missing required properties ([\"status\"]", testStepResults.First().Message);
250228

251229
// Retrieve event messages for the failing test case.
252-
var events = await _microcksContainerEnsemble.MicrocksContainer
230+
var events = await _fixture.MicrocksContainerEnsemble.MicrocksContainer
253231
.GetEventMessagesForTestCaseAsync(testResult, "SUBSCRIBE pastry/orders", TestContext.Current.CancellationToken);
254232
// We should have at least 4 events.
255233
Assert.True(events.Count >= 4);
@@ -263,4 +241,41 @@ public async Task ShouldReturnsCorrectStatusContractWhenBadMessageIsEmitted()
263241
});
264242
}
265243

266-
}
244+
// Inner fixture to share Kafka and Microcks ensemble between tests
245+
public sealed class KafkaFixture : IAsyncDisposable
246+
{
247+
private const string MicrocksImage = "quay.io/microcks/microcks-uber:1.10.0";
248+
249+
public INetwork Network { get; }
250+
public KafkaContainer KafkaContainer { get; }
251+
public MicrocksContainerEnsemble MicrocksContainerEnsemble { get; }
252+
253+
public KafkaFixture()
254+
{
255+
Network = new NetworkBuilder().Build();
256+
257+
KafkaContainer = new KafkaBuilder()
258+
.WithImage("confluentinc/cp-kafka:7.8.0")
259+
.WithNetwork(Network)
260+
.WithNetworkAliases("kafka")
261+
.WithListener("kafka:19092")
262+
.Build();
263+
264+
KafkaContainer.StartAsync().GetAwaiter().GetResult();
265+
266+
MicrocksContainerEnsemble = new MicrocksContainerEnsemble(Network, MicrocksImage)
267+
.WithMainArtifacts("pastry-orders-asyncapi.yml")
268+
.WithKafkaConnection(new KafkaConnection($"kafka:19092"));
269+
270+
MicrocksContainerEnsemble.StartAsync().GetAwaiter().GetResult();
271+
}
272+
273+
public async ValueTask DisposeAsync()
274+
{
275+
await MicrocksContainerEnsemble.DisposeAsync();
276+
await KafkaContainer.DisposeAsync();
277+
await Network.DisposeAsync();
278+
}
279+
}
280+
281+
}

tests/Microcks.Testcontainers.Tests/Async/MicrocksAsyncFeatureTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public async Task ShouldReturnsCorrectStatusContractWhenGoodMessageIsEmitted()
149149
Assert.True(string.IsNullOrEmpty(testResult.TestCaseResults.First().TestStepResults.First().Message));
150150
}
151151

152-
/// <summary>
152+
/// <summary>
153153
/// Test that verifies that the WaitForConditionAsync method throws a TaskCanceledException
154154
/// when the specified timeout is reached.
155155
/// </summary>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
using System;
19+
using DotNet.Testcontainers.Builders;
20+
using DotNet.Testcontainers.Containers;
21+
using DotNet.Testcontainers.Networks;
22+
23+
namespace Microcks.Testcontainers.Tests.Fixtures;
24+
25+
public sealed class MicrocksContractTestingFixture : IAsyncLifetime
26+
{
27+
private static readonly string BAD_PASTRY_IMAGE = "quay.io/microcks/contract-testing-demo:01";
28+
private static readonly string GOOD_PASTRY_IMAGE = "quay.io/microcks/contract-testing-demo:02";
29+
30+
public INetwork Network { get; }
31+
public MicrocksContainer MicrocksContainer { get; }
32+
public IContainer BadImpl { get; }
33+
public IContainer GoodImpl { get; }
34+
35+
public MicrocksContractTestingFixture()
36+
{
37+
Network = new NetworkBuilder().Build();
38+
39+
MicrocksContainer = new MicrocksBuilder()
40+
.WithNetwork(Network)
41+
.Build();
42+
43+
BadImpl = new ContainerBuilder()
44+
.WithImage(BAD_PASTRY_IMAGE)
45+
.WithNetwork(Network)
46+
.WithNetworkAliases("bad-impl")
47+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(".*Example app listening on port 3001.*"))
48+
.Build();
49+
50+
GoodImpl = new ContainerBuilder()
51+
.WithImage(GOOD_PASTRY_IMAGE)
52+
.WithNetwork(Network)
53+
.WithNetworkAliases("good-impl")
54+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(".*Example app listening on port 3002.*"))
55+
.Build();
56+
}
57+
58+
public async ValueTask InitializeAsync()
59+
{
60+
MicrocksContainer.Started +=
61+
(_, _) => MicrocksContainer.ImportAsMainArtifact("apipastries-openapi.yaml");
62+
63+
await Network.CreateAsync();
64+
await MicrocksContainer.StartAsync();
65+
await BadImpl.StartAsync();
66+
await GoodImpl.StartAsync();
67+
}
68+
69+
public async ValueTask DisposeAsync()
70+
{
71+
// Dispose of the containers in reverse order of creation
72+
await MicrocksContainer.DisposeAsync();
73+
await BadImpl.DisposeAsync();
74+
await GoodImpl.DisposeAsync();
75+
await Network.DisposeAsync();
76+
}
77+
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
namespace Microcks.Testcontainers.Tests.Fixtures;
19+
20+
[CollectionDefinition("MicrocksTests")]
21+
public class MicrocksTestCollection : ICollectionFixture<MicrocksContractTestingFixture>
22+
{
23+
// This class has no code, and is never created. Its purpose is simply
24+
// to be the place to apply [CollectionDefinition] and all the
25+
// ICollectionFixture<> interfaces.
26+
}

0 commit comments

Comments
 (0)