256 lines
9.2 KiB
C#
256 lines
9.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using NUnit.Framework;
|
|
using BaseGames.Core.Events;
|
|
using BaseGames.Core;
|
|
using UObject = UnityEngine.Object;
|
|
using UnityEngine;
|
|
|
|
namespace BaseGames.Tests.EditMode
|
|
{
|
|
/// <summary>
|
|
/// SO 事件系统 + ServiceLocator 单元测试(EditMode,无需 Play Mode)。
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class EventSystemTests
|
|
{
|
|
// ── EventSubscription ────────────────────────────────────────────────
|
|
|
|
[Test]
|
|
public void EventSubscription_Dispose_CallsUnsubscribeAction()
|
|
{
|
|
bool called = false;
|
|
var sub = new EventSubscription(() => called = true);
|
|
sub.Dispose();
|
|
Assert.IsTrue(called, "Dispose 应调用 unsubscribe action");
|
|
}
|
|
|
|
[Test]
|
|
public void EventSubscription_Dispose_NullAction_DoesNotThrow()
|
|
{
|
|
var sub = new EventSubscription(null);
|
|
Assert.DoesNotThrow(() => sub.Dispose());
|
|
}
|
|
|
|
// ── CompositeDisposable ──────────────────────────────────────────────
|
|
|
|
[Test]
|
|
public void CompositeDisposable_Clear_DisposesAllItems()
|
|
{
|
|
var composite = new CompositeDisposable();
|
|
int count = 0;
|
|
composite.Add(new EventSubscription(() => count++));
|
|
composite.Add(new EventSubscription(() => count++));
|
|
composite.Add(new EventSubscription(() => count++));
|
|
|
|
composite.Clear();
|
|
Assert.AreEqual(3, count, "Clear 应 Dispose 所有已添加的订阅");
|
|
}
|
|
|
|
[Test]
|
|
public void CompositeDisposable_Clear_TwiceSafe()
|
|
{
|
|
var composite = new CompositeDisposable();
|
|
int count = 0;
|
|
composite.Add(new EventSubscription(() => count++));
|
|
composite.Clear();
|
|
composite.Clear(); // 第二次 Clear 不应重复 Dispose
|
|
Assert.AreEqual(1, count, "Clear 两次不应重复 Dispose");
|
|
}
|
|
|
|
[Test]
|
|
public void CompositeDisposable_Dispose_SameAsClear()
|
|
{
|
|
var composite = new CompositeDisposable();
|
|
bool disposed = false;
|
|
composite.Add(new EventSubscription(() => disposed = true));
|
|
((IDisposable)composite).Dispose();
|
|
Assert.IsTrue(disposed);
|
|
}
|
|
|
|
[Test]
|
|
public void AddTo_ReturnsOriginalSubscription()
|
|
{
|
|
var composite = new CompositeDisposable();
|
|
bool called = false;
|
|
var sub = new EventSubscription(() => called = true);
|
|
var returned = sub.AddTo(composite);
|
|
returned.Dispose();
|
|
Assert.IsTrue(called, "AddTo 返回的订阅 Dispose 后应触发原 unsubscribe");
|
|
}
|
|
|
|
[Test]
|
|
public void AddTo_Collection_AddsToList()
|
|
{
|
|
var list = new List<IDisposable>();
|
|
bool called = false;
|
|
var sub = new EventSubscription(() => called = true);
|
|
sub.AddTo(list);
|
|
Assert.AreEqual(1, list.Count, "AddTo(ICollection) 应将订阅添加到列表");
|
|
list[0].Dispose();
|
|
Assert.IsTrue(called);
|
|
}
|
|
|
|
// ── IntEventChannelSO ────────────────────────────────────────────────
|
|
|
|
[Test]
|
|
public void IntEventChannel_Raise_CallsAllSubscribers()
|
|
{
|
|
var channel = ScriptableObject.CreateInstance<IntEventChannelSO>();
|
|
try
|
|
{
|
|
int sum = 0;
|
|
channel.OnEventRaised += v => sum += v;
|
|
channel.OnEventRaised += v => sum += v;
|
|
channel.Raise(5);
|
|
Assert.AreEqual(10, sum, "两个订阅者应各收到一次事件");
|
|
}
|
|
finally
|
|
{
|
|
UObject.DestroyImmediate(channel);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void IntEventChannel_Raise_NoSubscribers_DoesNotThrow()
|
|
{
|
|
var channel = ScriptableObject.CreateInstance<IntEventChannelSO>();
|
|
try
|
|
{
|
|
Assert.DoesNotThrow(() => channel.Raise(42));
|
|
}
|
|
finally
|
|
{
|
|
UObject.DestroyImmediate(channel);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void IntEventChannel_UnsubscribeAfterDispose()
|
|
{
|
|
var channel = ScriptableObject.CreateInstance<IntEventChannelSO>();
|
|
try
|
|
{
|
|
int callCount = 0;
|
|
void Handler(int v) => callCount++;
|
|
channel.OnEventRaised += Handler;
|
|
channel.Raise(1);
|
|
Assert.AreEqual(1, callCount);
|
|
|
|
channel.OnEventRaised -= Handler;
|
|
channel.Raise(1);
|
|
Assert.AreEqual(1, callCount, "取消订阅后不应再收到事件");
|
|
}
|
|
finally
|
|
{
|
|
UObject.DestroyImmediate(channel);
|
|
}
|
|
}
|
|
|
|
// ── VoidEventChannelSO ───────────────────────────────────────────────
|
|
|
|
[Test]
|
|
public void VoidEventChannel_Raise_CallsSubscriber()
|
|
{
|
|
var channel = ScriptableObject.CreateInstance<VoidEventChannelSO>();
|
|
try
|
|
{
|
|
bool received = false;
|
|
channel.OnEventRaised += () => received = true;
|
|
channel.Raise();
|
|
Assert.IsTrue(received);
|
|
}
|
|
finally
|
|
{
|
|
UObject.DestroyImmediate(channel);
|
|
}
|
|
}
|
|
|
|
// ── ServiceLocator ───────────────────────────────────────────────────
|
|
|
|
[SetUp]
|
|
public void SetUp() => ServiceLocator.Reset();
|
|
|
|
[TearDown]
|
|
public void TearDown() => ServiceLocator.Reset();
|
|
|
|
[Test]
|
|
public void ServiceLocator_RegisterAndGet_ReturnsInstance()
|
|
{
|
|
var mock = new MockService();
|
|
ServiceLocator.Register<IMockService>(mock);
|
|
var retrieved = ServiceLocator.Get<IMockService>();
|
|
Assert.AreSame(mock, retrieved);
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_GetUnregistered_ThrowsInvalidOperation()
|
|
{
|
|
Assert.Throws<InvalidOperationException>(
|
|
() => ServiceLocator.Get<IMockService>());
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_GetOrDefault_Unregistered_ReturnsDefault()
|
|
{
|
|
var result = ServiceLocator.GetOrDefault<IMockService>();
|
|
Assert.IsNull(result);
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_GetOrDefault_Registered_ReturnsInstance()
|
|
{
|
|
var mock = new MockService();
|
|
ServiceLocator.Register<IMockService>(mock);
|
|
var result = ServiceLocator.GetOrDefault<IMockService>();
|
|
Assert.AreSame(mock, result);
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_RegisterIfAbsent_DoesNotOverwrite()
|
|
{
|
|
var first = new MockService();
|
|
var second = new MockService();
|
|
ServiceLocator.Register<IMockService>(first);
|
|
ServiceLocator.RegisterIfAbsent<IMockService>(second);
|
|
Assert.AreSame(first, ServiceLocator.Get<IMockService>(),
|
|
"RegisterIfAbsent 不应覆盖已存在的注册");
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_Unregister_RemovesService()
|
|
{
|
|
ServiceLocator.Register<IMockService>(new MockService());
|
|
ServiceLocator.Unregister<IMockService>();
|
|
Assert.Throws<InvalidOperationException>(
|
|
() => ServiceLocator.Get<IMockService>());
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_Unregister_WithWrongInstance_DoesNotRemove()
|
|
{
|
|
var first = new MockService();
|
|
var second = new MockService();
|
|
ServiceLocator.Register<IMockService>(first);
|
|
ServiceLocator.Unregister<IMockService>(second); // 不同实例,不应移除
|
|
Assert.AreSame(first, ServiceLocator.Get<IMockService>(),
|
|
"错误实例的 Unregister 不应移除已注册的服务");
|
|
}
|
|
|
|
[Test]
|
|
public void ServiceLocator_OverrideForTest_ReplacesService()
|
|
{
|
|
var original = new MockService { Value = 1 };
|
|
var replacement = new MockService { Value = 99 };
|
|
ServiceLocator.Register<IMockService>(original);
|
|
ServiceLocator.OverrideForTest<IMockService>(replacement);
|
|
Assert.AreEqual(99, ServiceLocator.Get<IMockService>().Value);
|
|
}
|
|
|
|
// ── Test Helpers ─────────────────────────────────────────────────────
|
|
|
|
private interface IMockService { int Value { get; set; } }
|
|
private class MockService : IMockService { public int Value { get; set; } }
|
|
}
|
|
}
|