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; } }
|
||
}
|
||
}
|