Repository navigation
Guides Testing
Getting-Started | Guides-Patterns | Advanced-Runtime-Configuration
Test message delivery and registration ownership separately. Use a local bus for plain C# owners, and a scoped bus override when a Unity component resolves the global bus. Run Unity messaging tests on the main thread; separate buses still share runtime settings and pools.
This synchronous NUnit test uses no Unity object. Save it as PlainMessageTests.cs in a test assembly.
The token starts disabled, so enable it after staging the handler. The using block disposes the
registration even when an assertion fails.
using DxMessaging.Core;
using DxMessaging.Core.Extensions;
using DxMessaging.Core.MessageBus;
using DxMessaging.Core.Messages;
using NUnit.Framework;
public readonly struct TestPing : IUntargetedMessage<TestPing> { }
public sealed class PlainMessageTests
{
[Test]
public void LocalSubscriptionDeliversAndCleansUp()
{
MessageBus bus = new MessageBus();
MessageHandler handler = new MessageHandler(new InstanceId(1), bus) { active = true };
int received = 0;
TestPing message = new TestPing();
using (MessageRegistrationToken token = MessageRegistrationToken.Create(handler, bus))
{
_ = token.RegisterUntargeted<TestPing>((in TestPing ping) => received++);
token.Enable();
message.Emit(bus);
Assert.That(received, Is.EqualTo(1));
}
Assert.That(bus.RegisteredUntargeted, Is.Zero);
message.Emit(bus);
Assert.That(received, Is.EqualTo(1), "Disposal must remove the subscription.");
}
}Put these scripts and TestPing above in the same PlayMode test assembly. The receiver registers
through its inherited token and keeps the required base call. Save it as TestPingReceiver.cs:
using DxMessaging.Unity;
public sealed class TestPingReceiver : MessageAwareComponent
{
public int Received { get; private set; }
protected override void RegisterMessageHandlers()
{
base.RegisterMessageHandlers();
_ = Token.RegisterUntargeted<TestPing>(OnPing);
}
private void OnPing(in TestPing message)
{
Received++;
}
}Save the fixture as ComponentLifecycleTests.cs. The synchronous test checks enabled, disabled,
and re-enabled delivery. The receiver uses the default MessageRegistrationTiedToEnableStatus = true.
The fixture restores the previous global bus after destroying its GameObject:
using System;
using System.Collections;
using DxMessaging.Core;
using DxMessaging.Core.Extensions;
using DxMessaging.Core.MessageBus;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public sealed class ComponentLifecycleTests
{
private MessageBus _bus;
private IDisposable _busScope;
private GameObject _host;
[SetUp]
public void SetUp()
{
_bus = new MessageBus();
_busScope = MessageHandler.OverrideGlobalMessageBus(_bus);
}
[Test]
public void ReceiverTracksEnabledState()
{
_host = new GameObject("TestPingReceiver");
TestPingReceiver receiver = _host.AddComponent<TestPingReceiver>();
TestPing message = new TestPing();
message.Emit(_bus);
Assert.That(receiver.Received, Is.EqualTo(1));
receiver.enabled = false;
message.Emit(_bus);
Assert.That(receiver.Received, Is.EqualTo(1));
receiver.enabled = true;
message.Emit(_bus);
Assert.That(receiver.Received, Is.EqualTo(2));
}
[UnityTearDown]
public IEnumerator TearDown()
{
try
{
if (_host != null)
{
UnityEngine.Object.Destroy(_host);
}
yield return null;
_host = null;
Assert.That(_bus.RegisteredUntargeted, Is.Zero);
Assert.That(_bus.RegisteredTargeted, Is.Zero);
Assert.That(_bus.RegisteredBroadcast, Is.Zero);
Assert.That(_bus.RegisteredInterceptors, Is.Zero);
Assert.That(_bus.RegisteredPostProcessors, Is.Zero);
Assert.That(_bus.RegisteredGlobalAcceptAll, Is.Zero);
}
finally
{
_busScope.Dispose();
}
}
}Unity's Destroy contract
defers destruction until after the current Update loop. The
Unity teardown
yields once for that destruction,
then asserts the six registration counters. It does not poll until counts reach zero or reset the bus
to hide live registrations. A failed cleanup assertion still runs the finally block that restores
the previous bus.
Use [Test] for synchronous registration and delivery checks. Use [UnityTest] only when the test
must observe a later frame, scene transition, coroutine, or deferred destruction. Track every owned
GameObject and token so teardown runs even after an assertion fails.
- For Guides-Patterns#2-directed-commands-targeted, verify both the intended receiver and a different target that must not receive the message.
- For Guides-Patterns#3-observability-broadcast, verify the emitter's source and the intended source-bound and all-source listeners.
- For Guides-Patterns#4-validation-and-normalization-interceptors, test accepted, cancelled, and modified messages, including whether handlers and post-processors run.
- For cleanup and reentrancy, assert observable delivery and the registrations owned by the test. Avoid sleeps, retries, and unbounded waits for another fixture's state.
- Restore any diagnostics or runtime settings changed by a fixture. A local bus isolates routing; it does not isolate those shared settings.
- Getting-Started-Overview
- Getting-Started-Getting-Started
- Getting-Started-Install
- Getting-Started-Quick-Start
- Getting-Started-Visual-Guide
- Concepts-Message-Types
- Concepts-Listening-Patterns
- Concepts-Targeting-And-Context
- Concepts-Interceptors-And-Ordering
- Guides-Patterns
- Guides-Unity-Integration
- Guides-Testing
- Guides-Diagnostics
- Guides-Advanced
- Guides-Migration-Guide
- Advanced-Emit-Shorthands
- Advanced-Message-Bus-Providers
- Advanced-Runtime-Configuration
- Advanced-String-Messages
- Reference-Reference
- Reference-Quick-Reference
- Reference-Helpers
- Reference-Faq
- Reference-Glossary
- Reference-Troubleshooting
- Reference-Compatibility
Links