diff --git a/src/Akka.sln b/src/Akka.sln
index 1a5c4253abc..38eef547487 100644
--- a/src/Akka.sln
+++ b/src/Akka.sln
@@ -288,6 +288,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClusterToolsExample.Seed",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClusterToolsExample.Node", "examples\Cluster\ClusterTools\ClusterToolsExample.Node\ClusterToolsExample.Node.csproj", "{337A85B5-4A7C-4883-8634-46E7E52A765F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit2.Tests", "contrib\testkits\Akka.TestKit.Xunit2.Tests\Akka.TestKit.Xunit2.Tests.csproj", "{95017C99-E960-44E5-83AD-BF21461DF06F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1343,6 +1345,18 @@ Global
{337A85B5-4A7C-4883-8634-46E7E52A765F}.Release|x64.Build.0 = Release|Any CPU
{337A85B5-4A7C-4883-8634-46E7E52A765F}.Release|x86.ActiveCfg = Release|Any CPU
{337A85B5-4A7C-4883-8634-46E7E52A765F}.Release|x86.Build.0 = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|x64.Build.0 = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Debug|x86.Build.0 = Debug|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x64.ActiveCfg = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x64.Build.0 = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.ActiveCfg = Release|Any CPU
+ {95017C99-E960-44E5-83AD-BF21461DF06F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1469,6 +1483,7 @@ Global
{88D7D845-2F50-4D37-9026-B0A8353D0E8D} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C}
{ED00E6F4-2B5C-4F16-ADE4-45E4A73C17B8} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C}
{337A85B5-4A7C-4883-8634-46E7E52A765F} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C}
+ {95017C99-E960-44E5-83AD-BF21461DF06F} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164}
diff --git a/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Akka.TestKit.Xunit2.Tests.csproj b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Akka.TestKit.Xunit2.Tests.csproj
new file mode 100644
index 00000000000..49f9906bac1
--- /dev/null
+++ b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Akka.TestKit.Xunit2.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+ $(NetFrameworkTestVersion);$(NetTestVersion)
+
+
+
+
+
+
+
+
+
+
diff --git a/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Internals/AkkaEqualExceptionSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Internals/AkkaEqualExceptionSpec.cs
new file mode 100644
index 00000000000..a9f1b4b54ea
--- /dev/null
+++ b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/Internals/AkkaEqualExceptionSpec.cs
@@ -0,0 +1,33 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2025 Lightbend Inc.
+// Copyright (C) 2013-2025 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using Akka.TestKit.Xunit2.Internals;
+using Xunit;
+
+namespace Akka.TestKit.Xunit2.Tests.Internals;
+
+public static class AkkaEqualExceptionSpec
+{
+#if NETFRAMEWORK
+ [Fact]
+ public static void Constructor_deserializes_message()
+ {
+ var originalException = new AkkaEqualException("Test message");
+
+ AkkaEqualException deserializedException;
+ using (var memoryStream = new System.IO.MemoryStream())
+ {
+ var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
+ formatter.Serialize(memoryStream, originalException);
+ memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
+ deserializedException = (AkkaEqualException)formatter.Deserialize(memoryStream);
+ }
+
+ Assert.Equal(originalException.Message, deserializedException.Message);
+ }
+#endif
+}
diff --git a/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/XunitAssertionsSpec.cs b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/XunitAssertionsSpec.cs
new file mode 100644
index 00000000000..f96f64d6e43
--- /dev/null
+++ b/src/contrib/testkits/Akka.TestKit.Xunit2.Tests/XunitAssertionsSpec.cs
@@ -0,0 +1,81 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2025 Lightbend Inc.
+// Copyright (C) 2013-2025 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using Xunit;
+using Xunit.Sdk;
+
+namespace Akka.TestKit.Xunit2.Tests;
+
+public class XunitAssertionsSpec
+{
+ private readonly XunitAssertions _assertions = new();
+
+ [Fact]
+ public void Assert_does_not_format_message_when_no_arguments_are_specified()
+ {
+ const string testMessage = "{Value} with different format placeholders {0}";
+
+ var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage));
+ Assert.Contains(testMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage));
+ Assert.Contains(testMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage));
+ Assert.Contains(testMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage));
+ Assert.Contains(testMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage));
+ Assert.Contains(testMessage, exception.Message);
+ }
+
+ [Fact]
+ public void Assert_formats_message_when_arguments_are_specified()
+ {
+ const string testMessage = "Meaning: {0}";
+ const string expectedMessage = "Meaning: 42";
+
+ var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+ }
+
+ [Fact]
+ public void Assert_catches_format_exceptions()
+ {
+ const string testMessage = "Meaning: {0} {1}";
+ const string expectedMessage = "Could not string.Format";
+
+ var exception = Assert.ThrowsAny(() => _assertions.Fail(testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertTrue(false, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertFalse(true, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+
+ exception = Assert.ThrowsAny(() => _assertions.AssertEqual(4, 2, (_, _) => false, testMessage, 42));
+ Assert.Contains(expectedMessage, exception.Message);
+ }
+}