使用Splat的ReactiveUI应用并行单元测试冲突解决咨询
Great question! When using Splat alongside ReactiveUI in parallelized unit tests, the global static Locator can easily cause cross-test contamination from shared mocks or injected dependencies. The good news is Splat provides built-in mechanisms to create isolated DI contexts for each test—here are the most practical approaches:
1. Temporary Locator Replacement (Full Isolation)
The core fix is to swap out the global Locator with a fresh MutableDependencyResolver for each test, then restore the original resolver once the test finishes. This guarantees 100% isolation between parallel tests.
Here’s how to implement this with xUnit (which supports parallel testing by default):
private IMutableDependencyResolver _originalResolver; // Save the original resolver before any tests run in the class public MyTestClass() { _originalResolver = Locator.CurrentMutable; } [Fact] public void MyParallelSafeTest() { // Create a clean resolver just for this test var testResolver = new MutableDependencyResolver(); Locator.SetLocator(testResolver); try { // Register your test-specific mocks/services testResolver.RegisterConstant<IMyRequiredService>(new MockMyService()); // Initialize ReactiveUI services if your SUT uses them testResolver.InitializeReactiveUI(); // Run your test logic var sut = new MySystemUnderTest(); // ... perform assertions } finally { // Restore the original resolver to avoid breaking other tests Locator.SetLocator(_originalResolver); } }
2. Leverage Splat’s Test Mode (Simplified Isolation)
Splat has a dedicated test mode that streamlines setting up clean DI contexts. When enabled, it auto-replaces the global Locator with an empty resolver, and you can easily reset it between tests.
Use this with NUnit/xUnit setup/teardown methods:
[SetUp] // or [TestInitialize] for MSTest public void SetupTestContext() { // Initialize a clean Splat + ReactiveUI DI context Locator.CurrentMutable.InitializeSplat(); Locator.CurrentMutable.InitializeReactiveUI(); // Register any shared test dependencies here (if safe) } [TearDown] // or [TestCleanup] for MSTest public void CleanupTestContext() { // Reset the Locator to a fresh state for the next test Locator.SetLocator(new MutableDependencyResolver()); }
Pro Tip: Always call
InitializeReactiveUI()after setting up the test resolver—this ensures ReactiveUI’s internal services (like schedulers) are registered correctly for your test.
3. Fixture-Based Isolation (Shared Contexts)
If you need to share some base dependencies across tests in a class/collection but still keep parallel tests safe, use a fixture to manage the DI context lifecycle. This works great with xUnit’s IClassFixture or ICollectionFixture:
// Fixture that creates and cleans up an isolated DI context public class SplatTestFixture : IDisposable { public IMutableDependencyResolver TestResolver { get; } public SplatTestFixture() { TestResolver = new MutableDependencyResolver(); Locator.SetLocator(TestResolver); TestResolver.InitializeSplat(); TestResolver.InitializeReactiveUI(); } // Clean up the context when the fixture is disposed public void Dispose() { Locator.SetLocator(new MutableDependencyResolver()); } } // Use the fixture in your test class public class MyFeatureTests : IClassFixture<SplatTestFixture> { private readonly SplatTestFixture _fixture; public MyFeatureTests(SplatTestFixture fixture) { _fixture = fixture; } [Fact] public void TestScenarioOne() { // Register scenario-specific mocks _fixture.TestResolver.RegisterConstant<IMyService>(new MockServiceScenarioOne()); // ... test logic } [Fact] public void TestScenarioTwo() { _fixture.TestResolver.RegisterConstant<IMyService>(new MockServiceScenarioTwo()); // ... test logic } }
All these approaches center around ensuring each test runs with its own MutableDependencyResolver instance—no more shared state breaking your parallel tests! The first method gives full isolation, the second is the most concise, and the third balances shared dependencies with safety.
内容的提问来源于stack exchange,提问作者nikschilli

