diff --git a/Akka.sln b/Akka.sln
index 4838b6b2d75..e305fa35188 100644
--- a/Akka.sln
+++ b/Akka.sln
@@ -279,6 +279,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit2.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit.Tests", "src\contrib\testkits\Akka.TestKit.Xunit.Tests\Akka.TestKit.Xunit.Tests.csproj", "{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.TestKit.Xunit", "src\core\Akka.Persistence.TestKit.Xunit\Akka.Persistence.TestKit.Xunit.csproj", "{20C4E600-CF2C-4589-9005-BA82D962B312}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1318,6 +1320,18 @@ Global
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x64.Build.0 = Release|Any CPU
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.ActiveCfg = Release|Any CPU
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.Build.0 = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|x64.Build.0 = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Debug|x86.Build.0 = Debug|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|x64.ActiveCfg = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|x64.Build.0 = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|x86.ActiveCfg = Release|Any CPU
+ {20C4E600-CF2C-4589-9005-BA82D962B312}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1441,6 +1455,7 @@ Global
{337A85B5-4A7C-4883-8634-46E7E52A765F} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C}
{95017C99-E960-44E5-83AD-BF21461DF06F} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E}
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E}
+ {20C4E600-CF2C-4589-9005-BA82D962B312} = {01167D3C-49C4-4CDE-9787-C176D139ACDD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164}
diff --git a/src/core/Akka.Persistence.TestKit.Xunit/Akka.Persistence.TestKit.Xunit.csproj b/src/core/Akka.Persistence.TestKit.Xunit/Akka.Persistence.TestKit.Xunit.csproj
new file mode 100644
index 00000000000..f03af3156d8
--- /dev/null
+++ b/src/core/Akka.Persistence.TestKit.Xunit/Akka.Persistence.TestKit.Xunit.csproj
@@ -0,0 +1,17 @@
+
+
+
+ TestKit for writing tests for Akka.NET Persistance module using xUnit
+ $(NetStandardLibVersion);$(NetLibVersion)
+ $(AkkaPackageTags);testkit;persistance;xunit
+ true
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/core/Akka.Persistence.TestKit.Xunit/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit.Xunit/PersistenceTestKit.cs
new file mode 100644
index 00000000000..54ecf8b88f6
--- /dev/null
+++ b/src/core/Akka.Persistence.TestKit.Xunit/PersistenceTestKit.cs
@@ -0,0 +1,364 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2022 Lightbend Inc.
+// Copyright (C) 2013-2025 .NET Foundation
+//
+//-----------------------------------------------------------------------
+
+using Akka.Actor.Setup;
+
+namespace Akka.Persistence.TestKit;
+
+using System;
+using System.Threading.Tasks;
+using Actor;
+using Akka.TestKit.Xunit;
+using Configuration;
+using Xunit;
+
+///
+/// This class represents an Akka.NET Persistence TestKit that uses xUnit
+/// as its testing framework.
+///
+public class PersistenceTestKit : TestKit
+{
+ ///
+ /// Create a new instance of the class.
+ /// A new system with the specified configuration will be created.
+ ///
+ /// Test ActorSystem configuration
+ /// Optional: The name of the actor system
+ /// TBD
+ public PersistenceTestKit(ActorSystemSetup setup, string actorSystemName = null, ITestOutputHelper output = null)
+ : base(GetConfig(setup), actorSystemName, output)
+ {
+ var persistenceExtension = Persistence.Instance.Apply(Sys);
+
+ JournalActorRef = persistenceExtension.JournalFor(null);
+ Journal = TestJournal.FromRef(JournalActorRef);
+
+ SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null);
+ Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef);
+ }
+
+ ///
+ /// Create a new instance of the class.
+ /// A new system with the specified configuration will be created.
+ ///
+ /// Test ActorSystem configuration
+ /// Optional: The name of the actor system
+ /// TBD
+ public PersistenceTestKit(Config config, string actorSystemName = null, ITestOutputHelper output = null)
+ : base(GetConfig(config), actorSystemName, output)
+ {
+ var persistenceExtension = Persistence.Instance.Apply(Sys);
+
+ JournalActorRef = persistenceExtension.JournalFor(null);
+ Journal = TestJournal.FromRef(JournalActorRef);
+
+ SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null);
+ Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef);
+ }
+
+ public PersistenceTestKit(ActorSystem actorSystem, ITestOutputHelper output = null)
+ : base(actorSystem, output)
+ {
+ var persistenceExtension = Persistence.Instance.Apply(Sys);
+
+ JournalActorRef = persistenceExtension.JournalFor(null);
+ Journal = TestJournal.FromRef(JournalActorRef);
+
+ SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null);
+ Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef);
+ }
+
+ ///
+ /// Create a new instance of the class.
+ /// A new system with the default configuration will be created.
+ ///
+ /// Optional: The name of the actor system
+ /// TBD
+ public PersistenceTestKit(string actorSystemName = null, ITestOutputHelper output = null)
+ : this(Config.Empty, actorSystemName, output)
+ {
+ }
+
+ ///
+ /// Actor reference to persistence Journal used by current actor system.
+ ///
+ public IActorRef JournalActorRef { get; }
+
+ ///
+ /// Actor reference to persistence Snapshot Store used by current actor system.
+ ///
+ public IActorRef SnapshotsActorRef { get; }
+
+ ///
+ ///
+ ///
+ public ITestJournal Journal { get; }
+
+ ///
+ ///
+ ///
+ public ITestSnapshotStore Snapshots { get; }
+
+ ///
+ /// Execute delegate with Journal Behavior applied to Recovery operation.
+ ///
+ ///
+ /// After will be executed, Recovery behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Journal behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public async Task WithJournalRecovery(Func behaviorSelector, Func execution)
+ {
+ if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector));
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ try
+ {
+ await behaviorSelector(Journal.OnRecovery);
+ await execution();
+ }
+ finally
+ {
+ await Journal.OnRecovery.Pass();
+ }
+ }
+
+ ///
+ /// Execute delegate with Journal Behavior applied to Write operation.
+ ///
+ ///
+ /// After will be executed, Write behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Journal behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public async Task WithJournalWrite(Func behaviorSelector, Func execution)
+ {
+ if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector));
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ try
+ {
+ await behaviorSelector(Journal.OnWrite);
+ await execution();
+ }
+ finally
+ {
+ await Journal.OnWrite.Pass();
+ }
+ }
+
+ ///
+ /// Execute delegate with Journal Behavior applied to Recovery operation.
+ ///
+ ///
+ /// After will be executed, Recovery behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Journal behavior.
+ /// Delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public Task WithJournalRecovery(Func behaviorSelector, Action execution)
+ => WithJournalRecovery(behaviorSelector, () =>
+ {
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ execution();
+ return Task.FromResult(new object());
+ });
+
+ ///
+ /// Execute delegate with Journal Behavior applied to Write operation.
+ ///
+ ///
+ /// After will be executed, Write behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Journal behavior.
+ /// Delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public Task WithJournalWrite(Func behaviorSelector, Action execution)
+ => WithJournalWrite(behaviorSelector, () =>
+ {
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ execution();
+ return Task.FromResult(new object());
+ });
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Save operation.
+ ///
+ ///
+ /// After will be executed, Save behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public async Task WithSnapshotSave(Func behaviorSelector, Func execution)
+ {
+ if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector));
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ try
+ {
+ await behaviorSelector(Snapshots.OnSave);
+ await execution();
+ }
+ finally
+ {
+ await Snapshots.OnSave.Pass();
+ }
+ }
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Load operation.
+ ///
+ ///
+ /// After will be executed, Load behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public async Task WithSnapshotLoad(Func behaviorSelector, Func execution)
+ {
+ if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector));
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ try
+ {
+ await behaviorSelector(Snapshots.OnLoad);
+ await execution();
+ }
+ finally
+ {
+ await Snapshots.OnLoad.Pass();
+ }
+ }
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Delete operation.
+ ///
+ ///
+ /// After will be executed, Delete behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public async Task WithSnapshotDelete(Func behaviorSelector, Func execution)
+ {
+ if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector));
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ try
+ {
+ await behaviorSelector(Snapshots.OnDelete);
+ await execution();
+ }
+ finally
+ {
+ await Snapshots.OnDelete.Pass();
+ }
+ }
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Save operation.
+ ///
+ ///
+ /// After will be executed, Save behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public Task WithSnapshotSave(Func behaviorSelector, Action execution)
+ => WithSnapshotSave(behaviorSelector, () =>
+ {
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ execution();
+ return Task.FromResult(true);
+ });
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Load operation.
+ ///
+ ///
+ /// After will be executed, Load behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public Task WithSnapshotLoad(Func behaviorSelector, Action execution)
+ => WithSnapshotLoad(behaviorSelector, () =>
+ {
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ execution();
+ return Task.FromResult(true);
+ });
+
+ ///
+ /// Execute delegate with Snapshot Store Behavior applied to Delete operation.
+ ///
+ ///
+ /// After will be executed, Delete behavior will be reverted back to normal.
+ ///
+ /// Delegate which will select Snapshot Store behavior.
+ /// Async delegate which will be executed with applied Journal behavior.
+ /// which must be awaited.
+ public Task WithSnapshotDelete(Func behaviorSelector, Action execution)
+ => WithSnapshotDelete(behaviorSelector, () =>
+ {
+ if (execution == null) throw new ArgumentNullException(nameof(execution));
+
+ execution();
+ return Task.FromResult(true);
+ });
+
+ ///
+ /// Loads from embedded resources actor system persistence configuration with and
+ /// configured as default persistence plugins.
+ ///
+ /// Custom configuration that was passed in the constructor.
+ /// Actor system configuration object.
+ ///
+ private static ActorSystemSetup GetConfig(ActorSystemSetup customConfig)
+ {
+ var bootstrapSetup = customConfig.Get();
+ var config = bootstrapSetup.FlatSelect(x => x.Config);
+ var actorProvider = bootstrapSetup.FlatSelect(x => x.ActorRefProvider);
+ var newSetup = BootstrapSetup.Create();
+ if (config.HasValue)
+ {
+ newSetup = newSetup.WithConfig(GetConfig(config.Value));
+ }
+ else
+ {
+ newSetup = newSetup.WithConfig(GetConfig(Config.Empty));
+ }
+
+ if (actorProvider.HasValue)
+ {
+ newSetup = newSetup.WithActorRefProvider(actorProvider.Value);
+ }
+
+ return customConfig.WithSetup(newSetup);
+ }
+
+ ///
+ /// Loads from embedded resources actor system persistence configuration with and
+ /// configured as default persistence plugins.
+ ///
+ /// Custom configuration that was passed in the constructor.
+ /// Actor system configuration object.
+ ///
+ private static Config GetConfig(Config customConfig)
+ {
+ var defaultConfig = ConfigurationFactory.FromResource("Akka.Persistence.TestKit.config.conf");
+ if (customConfig == Config.Empty) return defaultConfig;
+ else return defaultConfig.SafeWithFallback(customConfig);
+ }
+}
\ No newline at end of file
diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj
index b09dc51fc99..20e579d629a 100644
--- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj
+++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj
@@ -12,7 +12,6 @@
-