单元测试使用C#的单元测试,一切与C#无关的平台统一使用接口,通过依赖注入和mock使用
案例: 微信小程序Storage单元测试
using System.Text.Json; using System.Threading.Tasks; using TsRoslyn.Core.Api; using TsRoslyn.Razor.Core; using XieYun.Application.Interfaces; namespace XieYun.Application.Services; // 微信api抽象为IWxApi。隔离js和C# public class LocalStore<T>(IWxApi wxApi) : IStore<T> where T : class { private bool IsInitialized { get; set; } private T? Data { get; set; } public async Task<T?> GetAsync() { if (IsInitialized) return Data; var storeDataString = await wxApi.GetStorageAsync(typeof(T).ToString()); T? result = null; if (storeDataString is not null) { result = JsonSerializer.Deserialize<T>(storeDataString); } Data = result; IsInitialized = true; return result; } public Task SetAsync(T value) { Data = value; IsInitialized = true; return wxApi.SetStorageAsync(typeof(T).ToString(), JsonSerializer.Serialize(value)); } }接下来使用ai,生成单元测试用例
using System.Text.Json; using Moq; using TsRoslyn.Core.Api; using XieYun.Application.Services; namespace XieYun.Application.Test.Services; public class TestData { public required string Name { get; init; } public required int Value { get; init; } } [TestFixture] [TestOf(typeof(LocalStore<>))] public class LocalStoreTest { private Mock<IWxApi> _wxApiMock; private LocalStore<TestData> _localStore; [SetUp] public void Setup() { _wxApiMock = new Mock<IWxApi>(); _localStore = new LocalStore<TestData>(_wxApiMock.Object); } [Test] public async Task GetAsync_WhenNotInitializedAndNoStorageData_ReturnsNull() { // Arrange _wxApiMock.Setup(x => x.GetStorageAsync(typeof(TestData).ToString())) .ReturnsAsync((string?)null); // Act var result = await _localStore.GetAsync(); // Assert Assert.That(result, Is.Null); _wxApiMock.Verify(x => x.GetStorageAsync(typeof(TestData).ToString()), Times.Once); } [Test] public async Task GetAsync_WhenNotInitializedAndHasStorageData_ReturnsDeserializedData() { // Arrange var testData = new TestData { Name = "Test", Value = 123 }; var serializedData = JsonSerializer.Serialize(testData); _wxApiMock.Setup(x => x.GetStorageAsync(typeof(TestData).ToString())) .ReturnsAsync(serializedData); // Act var result = await _localStore.GetAsync(); // Assert Assert.That(result, Is.Not.Null); Assert.That(result.Name, Is.EqualTo(testData.Name)); Assert.That(result.Value, Is.EqualTo(testData.Value)); _wxApiMock.Verify(x => x.GetStorageAsync(typeof(TestData).ToString()), Times.Once); } [Test] public async Task GetAsync_WhenAlreadyInitialized_ReturnsCachedDataWithoutApiCall() { // Arrange var testData = new TestData { Name = "Cached", Value = 456 }; _localStore.SetAsync(testData).Wait(); // Initialize and cache data // Act var result = await _localStore.GetAsync(); // Assert Assert.That(result, Is.Not.Null); Assert.That(result.Name, Is.EqualTo(testData.Name)); Assert.That(result.Value, Is.EqualTo(testData.Value)); _wxApiMock.Verify(x => x.GetStorageAsync(It.IsAny<string>()), Times.Never); } [Test] public async Task SetAsync_SerializesDataAndStoresInApi() { // Arrange var testData = new TestData { Name = "New", Value = 789 }; string? storedValue = null; _wxApiMock.Setup(x => x.SetStorageAsync(typeof(TestData).ToString(), It.IsAny<string>())) .Callback<string, string>((_, value) => storedValue = value) .Returns(Task.CompletedTask); // Act await _localStore.SetAsync(testData); // Assert _wxApiMock.Verify(x => x.SetStorageAsync(typeof(TestData).ToString(), It.IsAny<string>()), Times.Once); var deserialized = JsonSerializer.Deserialize<TestData>(storedValue); Assert.That(deserialized, Is.Not.Null); Assert.That(deserialized.Name, Is.EqualTo(testData.Name)); Assert.That(deserialized.Value, Is.EqualTo(testData.Value)); } [Test] public async Task SetAsync_UpdatesCachedData() { // Arrange var initialData = new TestData { Name = "Initial", Value = 111 }; var newData = new TestData { Name = "Updated", Value = 222 }; await _localStore.SetAsync(initialData); // Act await _localStore.SetAsync(newData); var result = await _localStore.GetAsync(); // Assert Assert.That(result, Is.Not.Null); Assert.That(result.Name, Is.EqualTo(newData.Name)); Assert.That(result.Value, Is.EqualTo(newData.Value)); } [Test] public async Task GetAsync_AfterSet_ReturnsCachedDataWithoutApiCall() { // Arrange var testData = new TestData { Name = "Cached", Value = 333 }; await _localStore.SetAsync(testData); // Act var result = await _localStore.GetAsync(); // Assert Assert.That(result, Is.Not.Null); _wxApiMock.Verify(x => x.GetStorageAsync(It.IsAny<string>()), Times.Never); } [Test] public async Task MultipleGets_OnlyFetchesFromApiOnce() { // Arrange var testData = new TestData { Name = "Test", Value = 444 }; var serializedData = JsonSerializer.Serialize(testData); _wxApiMock.Setup(x => x.GetStorageAsync(typeof(TestData).ToString())) .ReturnsAsync(serializedData); // Act await _localStore.GetAsync(); await _localStore.GetAsync(); await _localStore.GetAsync(); // Assert _wxApiMock.Verify(x => x.GetStorageAsync(typeof(TestData).ToString()), Times.Once); } }这样子就可以很轻松的使用C#的原生能力,进行测试。
应用项目里通过依赖注入,就能把js注入进来
serviceCollection.AddSingleton<IWxApi, WxApi>(); serviceCollection.AddSingleton<IStore<UserInfoStoreData>, LocalStore<UserInfoStoreData>>();