Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3f8880e
Added initial project
WhitWaldo Dec 20, 2025
42b9ac2
Initial commit of the testcontainer functionality
WhitWaldo Dec 20, 2025
b4623e0
Updated to apply a configuration for environment variables to be set …
WhitWaldo Dec 20, 2025
d0b2e0d
Updated to use the app port reported by the test app instead of using…
WhitWaldo Dec 20, 2025
7bf491c
Minor refactoring
WhitWaldo Dec 20, 2025
b63579e
Added support for using a shared Docker network across the containers…
WhitWaldo Dec 20, 2025
68f954b
Added a cleanup step to remove old generated YAML files
WhitWaldo Dec 20, 2025
fb1f24b
Added ability to set app ID via options, but it'll naturally pick a r…
WhitWaldo Dec 21, 2025
668a432
Standardized component named to use constants for each building block…
WhitWaldo Dec 21, 2025
ec7be5b
Updated to assign a random port to the test app when starting up
WhitWaldo Dec 21, 2025
f0acbdd
Changed wait condition so Dapr Placement service properly initializes
WhitWaldo Dec 21, 2025
7c54199
Changed wait strategy and volume mount approach so Dapr Scheduler ser…
WhitWaldo Dec 21, 2025
628dd49
Updated harnesses to make the start app optional
WhitWaldo Dec 22, 2025
dfc4579
Updated the daprd container to ensure it starts up as expected
WhitWaldo Dec 22, 2025
bda9f91
Updated all the harnesses to expose the App Port so it's accessible w…
WhitWaldo Dec 22, 2025
295eb67
Updated all the containers to expose a network alias value that refle…
WhitWaldo Dec 22, 2025
c742bae
Updating the various harnesses to use the network alias from the harn…
WhitWaldo Dec 22, 2025
3c08d97
Updated harnesses to make better use of the base class to avoid repea…
WhitWaldo Dec 23, 2025
09309d5
Updated the base harness to address an out-of-order bug
WhitWaldo Dec 23, 2025
0c8e33c
Updated the default version to use the "latest" tag instead of a hard…
WhitWaldo Dec 23, 2025
6d41ffe
Setting environment variable endpoint instead of port
WhitWaldo Dec 23, 2025
a2f13b7
Changed the logged message as the one there only shows up for CLI lau…
WhitWaldo Dec 23, 2025
1f23ab5
Applied several changes to remedy errors in TestContainer implementation
WhitWaldo Dec 23, 2025
5dadf4e
Added more projects for E2E testing, including Jobs, PubSub and Workf…
WhitWaldo Dec 23, 2025
7cc41bd
Simplified harness implementation to remove some redunant cleanup
WhitWaldo Dec 24, 2025
666cf1d
Simplified how E2E tests are built against fluent helper for app setu…
WhitWaldo Dec 24, 2025
e09a879
Tweaks to start using separate networks for each test in preparation …
WhitWaldo Dec 24, 2025
1f0f52a
Tweaked jobs test to also validate job name
WhitWaldo Dec 24, 2025
d7e6f07
Made some tweaks to ensure that the app is connecting to Dapr instanc…
WhitWaldo Dec 24, 2025
a7cf2e4
Removed unused using statement
WhitWaldo Dec 24, 2025
e682aab
Removed invalid target framework identifier
WhitWaldo Dec 24, 2025
f23f61f
Added some commented out sample E2E tests from an earlier build so th…
WhitWaldo Dec 24, 2025
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 Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="Testcontainers" Version="4.9.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
Expand Down
30 changes: 29 additions & 1 deletion all.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32929.385
MinimumVisualStudioVersion = 10.0.40219.1
Expand Down Expand Up @@ -205,6 +205,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DistributedLock", "Distribu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistributedLock", "examples\DistributedLock\DistributedLock\DistributedLock.csproj", "{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.TestContainers", "src\Dapr.TestContainers\Dapr.TestContainers.csproj", "{D14893E1-EF21-4EB4-9DAD-82B5127832CB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.PubSub", "test\Dapr.E2E.Test.PubSub\Dapr.E2E.Test.PubSub.csproj", "{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.Workflow", "test\Dapr.E2E.Test.Workflow\Dapr.E2E.Test.Workflow.csproj", "{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.Jobs", "test\Dapr.E2E.Test.Jobs\Dapr.E2E.Test.Jobs.csproj", "{775302E3-69CC-4FBD-98CC-0F889A5D4C63}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -541,6 +549,22 @@ Global
{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Release|Any CPU.Build.0 = Release|Any CPU
{D14893E1-EF21-4EB4-9DAD-82B5127832CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D14893E1-EF21-4EB4-9DAD-82B5127832CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D14893E1-EF21-4EB4-9DAD-82B5127832CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D14893E1-EF21-4EB4-9DAD-82B5127832CB}.Release|Any CPU.Build.0 = Release|Any CPU
{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E}.Release|Any CPU.Build.0 = Release|Any CPU
{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB}.Release|Any CPU.Build.0 = Release|Any CPU
{775302E3-69CC-4FBD-98CC-0F889A5D4C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{775302E3-69CC-4FBD-98CC-0F889A5D4C63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{775302E3-69CC-4FBD-98CC-0F889A5D4C63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{775302E3-69CC-4FBD-98CC-0F889A5D4C63}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -640,6 +664,10 @@ Global
{9BD12D26-AD9B-4C76-A97F-7A89B7276ABE} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{11D2CA0F-6D38-4DC7-AE06-C1DAE7FC1C20} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93} = {11D2CA0F-6D38-4DC7-AE06-C1DAE7FC1C20}
{D14893E1-EF21-4EB4-9DAD-82B5127832CB} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{4997B2D1-49AF-40B7-8D84-4BD1E94D9B2E} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{3CE069E8-9AAB-4DE4-90AC-077C1DB4EAEB} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{775302E3-69CC-4FBD-98CC-0F889A5D4C63} = {DD020B34-460F-455F-8D17-CF4A949F100B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
Expand Down
83 changes: 83 additions & 0 deletions src/Dapr.TestContainers/Common/DaprHarnessBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Dapr.E2E.Test.Common;
using Dapr.TestContainers.Common.Options;
using Dapr.TestContainers.Harnesses;

namespace Dapr.TestContainers.Common;

/// <summary>
/// Builds the Dapr harnesses for different building blocks.
/// </summary>
/// <param name="options">The Dapr runtime options.</param>
/// <param name="startApp">The test app to run.</param>
public sealed class DaprHarnessBuilder(DaprRuntimeOptions options, Func<int, Task>? startApp = null)
{
/// <summary>
/// Builds a workflow harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public WorkflowHarness BuildWorkflow(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds a distributed lock harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public DistributedLockHarness BuildDistributedLock(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds a conversation harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public ConversationHarness BuildConversation(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds a cryptography harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
/// <param name="keysDir">The path to the cryptography keys.</param>
public CryptographyHarness BuildCryptography(string componentsDir, string keysDir) =>
new(componentsDir, startApp, keysDir, options);

/// <summary>
/// Builds a jobs harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public JobsHarness BuildJobs(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds a PubSub harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public PubSubHarness BuildPubSub(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds a state management harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public StateManagementHarness BuildStateManagement(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Builds an actor harness.
/// </summary>
/// <param name="componentsDir">The path to the Dapr resources.</param>
public ActorHarness BuildActors(string componentsDir) => new(componentsDir, startApp, options);

/// <summary>
/// Creates a test application builder for the specified harness.
/// </summary>
public static DaprTestApplicationBuilder ForHarness(BaseHarness harness) => new(harness);
}
25 changes: 25 additions & 0 deletions src/Dapr.TestContainers/Common/HostPortPair.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.TestContainers.Common;

/// <summary>
/// A hostname and port value pair.
/// </summary>
public sealed record HostPortPair(string Hostname, int Port)
{
/// <summary>
/// Provides a string version of the record.
/// </summary>
public override string ToString() => $"{Hostname}:{Port}";
}
27 changes: 27 additions & 0 deletions src/Dapr.TestContainers/Common/IAsyncContainerFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Dapr.TestContainers.Common;

/// <summary>
/// Represents a harness that's created for test purposes.
/// </summary>
public interface IAsyncContainerFixture : IAsyncDisposable
{
/// <inheritdoc />
Task InitializeAsync(CancellationToken cancellationToken = default);
}
36 changes: 36 additions & 0 deletions src/Dapr.TestContainers/Common/IAsyncStartable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Dapr.TestContainers.Common;

/// <summary>
/// Represents a resource that can be started and stopped.
/// </summary>
public interface IAsyncStartable : IAsyncDisposable
{
/// <summary>
/// Starts the resource.
/// </summary>
/// <param name="cancellationToken">Cancellation token/</param>
Task StartAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Stops the resource.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task StopAsync(CancellationToken cancellationToken = default);
}
45 changes: 45 additions & 0 deletions src/Dapr.TestContainers/Common/Options/DaprLogLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.TestContainers.Common.Options;

/// <summary>
/// A configurable log level.
/// </summary>
public enum DaprLogLevel
{
/// <summary>
/// Represents a "debug" log level.
/// </summary>
Debug,
/// <summary>
/// Represents an "info" log level.
/// </summary>
Info,
/// <summary>
/// Represents a "warn" log level.
/// </summary>
Warn,
/// <summary>
/// Represents an "error" log level.
/// </summary>
Error,
/// <summary>
/// Represents a "fatal" log level.
/// </summary>
Fatal,
/// <summary>
/// Represents a "panic" log level.
/// </summary>
Panic
}
71 changes: 71 additions & 0 deletions src/Dapr.TestContainers/Common/Options/DaprRuntimeOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System;

namespace Dapr.TestContainers.Common.Options;

/// <summary>
/// The various options used to spin up the Dapr containers.
/// </summary>
/// <param name="Version">The version of the Dapr images to use.</param>
public sealed record DaprRuntimeOptions(string Version = "latest")
{
/// <summary>
/// The application's port.
/// </summary>
public int AppPort { get; set; } = 8080;

/// <summary>
/// The ID of the test application.
/// </summary>
public string AppId { get; private set; } = $"test-app-{Guid.NewGuid():N}";

/// <summary>
/// The level of Dapr logs to show.
/// </summary>
public DaprLogLevel LogLevel { get; private set; } = DaprLogLevel.Info;

/// <summary>
/// The image tag for the Dapr runtime.
/// </summary>
public string RuntimeImageTag => $"daprio/daprd:{Version}";
/// <summary>
/// The image tag for the Dapr placement service.
/// </summary>
public string PlacementImageTag => $"daprio/placement:{Version}";
/// <summary>
/// The image tag for the Dapr scheduler service.
/// </summary>
public string SchedulerImageTag => $"daprio/scheduler:{Version}";

/// <summary>
/// Sets the Dapr log level.
/// </summary>
/// <param name="logLevel">The log level to specify.</param>
public DaprRuntimeOptions WithLogLevel(DaprLogLevel logLevel)
{
LogLevel = logLevel;
return this;
}

/// <summary>
/// Sets the Dapr App ID.
/// </summary>
/// <param name="appId">The App ID to use for the test application.</param>
public DaprRuntimeOptions WithAppId(string appId)
{
AppId = appId;
return this;
}
}
34 changes: 34 additions & 0 deletions src/Dapr.TestContainers/Common/PortUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Net;
using System.Net.Sockets;

namespace Dapr.TestContainers.Common;

/// <summary>
/// Provides port-related utilities.
/// </summary>
internal static class PortUtilities
{
/// <summary>
/// Finds a port that's available to use.
/// </summary>
/// <returns>The available port number.</returns>
public static int GetAvailablePort()
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
return ((IPEndPoint)socket.LocalEndPoint!).Port;
}
}
Loading