mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(functional): 增强Result类型功能并添加完整测试覆盖
- 为Result.Failure方法添加参数验证,确保异常和消息参数不为空 - 重构Result<T>内部状态枚举顺序以优化比较逻辑 - 为Result<T>的Map、Bind和MapAsync方法添加参数验证和异常处理 - 在Result<T>比较逻辑中添加类型不可比较时的安全处理 - 添加ResultExtensions.BindAsync扩展方法支持异步绑定操作 - 重写Combine扩展方法中对失败结果的处理逻辑 - 添加完整的Result相关类型单元测试覆盖所有功能场景 - 为Result类型添加详细的XML文档注释和使用示例
This commit is contained in:
parent
1cb7dfdb14
commit
61349a83ab
496
GFramework.Core.Tests/extensions/ResultExtensionsTests.cs
Normal file
496
GFramework.Core.Tests/extensions/ResultExtensionsTests.cs
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
// Copyright (c) 2025 GeWuYou
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using GFramework.Core.Extensions;
|
||||||
|
using GFramework.Core.Functional;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ResultExtensions 扩展方法测试类
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ResultExtensionsTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Return_Success_List_When_All_Results_Succeed()
|
||||||
|
{
|
||||||
|
var results = new[]
|
||||||
|
{
|
||||||
|
Result<int>.Succeed(1),
|
||||||
|
Result<int>.Succeed(2),
|
||||||
|
Result<int>.Succeed(3)
|
||||||
|
};
|
||||||
|
var combined = results.Combine();
|
||||||
|
Assert.That(combined.IsSuccess, Is.True);
|
||||||
|
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
|
||||||
|
Assert.That(list, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(list, Is.EqualTo(new[] { 1, 2, 3 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Return_First_Failure_When_Any_Result_Fails()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var results = new[]
|
||||||
|
{
|
||||||
|
Result<int>.Succeed(1),
|
||||||
|
Result<int>.Fail(exception),
|
||||||
|
Result<int>.Succeed(3)
|
||||||
|
};
|
||||||
|
var combined = results.Combine();
|
||||||
|
Assert.That(combined.IsFaulted, Is.True);
|
||||||
|
Assert.That(combined.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Handle_Empty_Collection()
|
||||||
|
{
|
||||||
|
var results = Array.Empty<Result<int>>();
|
||||||
|
var combined = results.Combine();
|
||||||
|
Assert.That(combined.IsSuccess, Is.True);
|
||||||
|
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
|
||||||
|
Assert.That(list, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Throw_ArgumentNullException_When_Collection_Is_Null()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => ((IEnumerable<Result<int>>)null!).Combine());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Preserve_Order_Of_Values()
|
||||||
|
{
|
||||||
|
var results = new[]
|
||||||
|
{
|
||||||
|
Result<int>.Succeed(3),
|
||||||
|
Result<int>.Succeed(1),
|
||||||
|
Result<int>.Succeed(2)
|
||||||
|
};
|
||||||
|
var combined = results.Combine();
|
||||||
|
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
|
||||||
|
Assert.That(list, Is.EqualTo(new[] { 3, 1, 2 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Combine_Should_Short_Circuit_On_First_Failure()
|
||||||
|
{
|
||||||
|
var callCount = 0;
|
||||||
|
var results = new[]
|
||||||
|
{
|
||||||
|
Result<int>.Succeed(1),
|
||||||
|
Result<int>.Fail(new Exception("Error")),
|
||||||
|
Result<int>.Try(() =>
|
||||||
|
{
|
||||||
|
callCount++;
|
||||||
|
return 3;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
var combined = results.Combine();
|
||||||
|
Assert.That(combined.IsFaulted, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Transform_Success_Value()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var mapped = result.Map(x => x.ToString());
|
||||||
|
Assert.That(mapped.IsSuccess, Is.True);
|
||||||
|
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Propagate_Failure()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var mapped = result.Map(x => x.ToString());
|
||||||
|
Assert.That(mapped.IsFaulted, Is.True);
|
||||||
|
Assert.That(mapped.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Throw_ArgumentNullException_When_Mapper_Is_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.Map<string>(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Chain_Success_Results()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
|
||||||
|
Assert.That(bound.IsSuccess, Is.True);
|
||||||
|
Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Propagate_Failure()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
|
||||||
|
Assert.That(bound.IsFaulted, Is.True);
|
||||||
|
Assert.That(bound.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Throw_ArgumentNullException_When_Binder_Is_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.Bind<string>(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnSuccess_Should_Execute_Action_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var executed = false;
|
||||||
|
result.OnSuccess(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnSuccess_Should_Not_Execute_Action_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var executed = false;
|
||||||
|
result.OnSuccess(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnSuccess_Should_Return_Original_Result_For_Chaining()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var returned = result.OnSuccess(_ => { });
|
||||||
|
Assert.That(returned, Is.EqualTo(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnSuccess_Should_Throw_ArgumentNullException_When_Action_Is_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.OnSuccess(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnFailure_Should_Execute_Action_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var executed = false;
|
||||||
|
result.OnFailure(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnFailure_Should_Not_Execute_Action_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var executed = false;
|
||||||
|
result.OnFailure(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnFailure_Should_Return_Original_Result_For_Chaining()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var returned = result.OnFailure(_ => { });
|
||||||
|
Assert.That(returned, Is.EqualTo(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OnFailure_Should_Throw_ArgumentNullException_When_Action_Is_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.OnFailure(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Return_Success_When_Predicate_Is_True()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var ensured = result.Ensure(x => x > 0, "Value must be positive");
|
||||||
|
Assert.That(ensured.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Return_Failure_When_Predicate_Is_False()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(-1);
|
||||||
|
var ensured = result.Ensure(x => x > 0, "Value must be positive");
|
||||||
|
Assert.That(ensured.IsFaulted, Is.True);
|
||||||
|
Assert.That(ensured.Exception, Is.TypeOf<ArgumentException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Propagate_Existing_Failure()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Original error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var ensured = result.Ensure(x => x > 0, "Value must be positive");
|
||||||
|
Assert.That(ensured.IsFaulted, Is.True);
|
||||||
|
Assert.That(ensured.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Create_ArgumentException_With_Message()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(-1);
|
||||||
|
var ensured = result.Ensure(x => x > 0, "Value must be positive");
|
||||||
|
Assert.That(ensured.Exception.Message, Is.EqualTo("Value must be positive"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Throw_ArgumentNullException_When_Predicate_Is_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.Ensure(null!, "Error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_Should_Throw_ArgumentException_When_Message_Is_NullOrWhiteSpace()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentException>(() => result.Ensure(x => true, ""));
|
||||||
|
Assert.Throws<ArgumentException>(() => result.Ensure(x => true, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_WithFactory_Should_Return_Success_When_Predicate_Is_True()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Error"));
|
||||||
|
Assert.That(ensured.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_WithFactory_Should_Return_Failure_With_Custom_Exception()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(-1);
|
||||||
|
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Custom error"));
|
||||||
|
Assert.That(ensured.IsFaulted, Is.True);
|
||||||
|
Assert.That(ensured.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_WithFactory_Should_Pass_Value_To_Exception_Factory()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(-1);
|
||||||
|
var capturedValue = 0;
|
||||||
|
result.Ensure(x => x > 0, v =>
|
||||||
|
{
|
||||||
|
capturedValue = v;
|
||||||
|
return new Exception("Error");
|
||||||
|
});
|
||||||
|
Assert.That(capturedValue, Is.EqualTo(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_WithFactory_Should_Propagate_Existing_Failure()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Original error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Error"));
|
||||||
|
Assert.That(ensured.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Ensure_WithFactory_Should_Throw_ArgumentNullException_When_Parameters_Are_Null()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.Ensure(null!, (Func<int, Exception>)(_ => new Exception())));
|
||||||
|
Assert.Throws<ArgumentNullException>(() => result.Ensure(x => true, (Func<int, Exception>)null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Try_Should_Return_Success_When_Function_Succeeds()
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.Try(() => 42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Try_Should_Return_Failure_When_Function_Throws()
|
||||||
|
{
|
||||||
|
var result = ResultExtensions.Try<int>(() => throw new InvalidOperationException("Error"));
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Try_Should_Throw_ArgumentNullException_When_Function_Is_Null()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => ResultExtensions.Try<int>(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryAsync_Should_Return_Success_When_Async_Function_Succeeds()
|
||||||
|
{
|
||||||
|
var result = await ResultExtensions.TryAsync(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1);
|
||||||
|
return 42;
|
||||||
|
});
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryAsync_Should_Return_Failure_When_Async_Function_Throws()
|
||||||
|
{
|
||||||
|
var result = await ResultExtensions.TryAsync<int>(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1);
|
||||||
|
throw new InvalidOperationException("Error");
|
||||||
|
});
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryAsync_Should_Handle_Synchronous_Exceptions()
|
||||||
|
{
|
||||||
|
var result = await ResultExtensions.TryAsync<int>(() => throw new InvalidOperationException("Sync error"));
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryAsync_Should_Throw_ArgumentNullException_When_Function_Is_Null()
|
||||||
|
{
|
||||||
|
Assert.ThrowsAsync<ArgumentNullException>(async () => await ResultExtensions.TryAsync<int>(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToNullable_Should_Return_Value_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var nullable = result.ToNullable();
|
||||||
|
Assert.That(nullable, Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToNullable_Should_Return_Null_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var nullable = result.ToNullable();
|
||||||
|
Assert.That(nullable, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToNullable_Should_Work_With_Value_Types()
|
||||||
|
{
|
||||||
|
var result = Result<DateTime>.Succeed(new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
||||||
|
var nullable = result.ToNullable();
|
||||||
|
Assert.That(nullable, Is.Not.Null);
|
||||||
|
Assert.That(nullable!.Value.Year, Is.EqualTo(2025));
|
||||||
|
Assert.That(nullable!.Value.Kind, Is.EqualTo(DateTimeKind.Utc)); // 验证 DateTimeKind
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Return_Success_When_Value_Is_Not_Null()
|
||||||
|
{
|
||||||
|
string value = "test";
|
||||||
|
var result = value.ToResult();
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => ""), Is.EqualTo("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Return_Failure_When_Value_Is_Null()
|
||||||
|
{
|
||||||
|
string? value = null;
|
||||||
|
var result = value.ToResult();
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<ArgumentNullException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Use_Custom_Error_Message()
|
||||||
|
{
|
||||||
|
string? value = null;
|
||||||
|
var result = value.ToResult("Custom error");
|
||||||
|
Assert.That(result.Exception.Message, Does.Contain("Custom error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Return_Success_When_Nullable_Has_Value()
|
||||||
|
{
|
||||||
|
int? value = 42;
|
||||||
|
var result = value.ToResult();
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Return_Failure_When_Nullable_Is_Null()
|
||||||
|
{
|
||||||
|
int? value = null;
|
||||||
|
var result = value.ToResult();
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<ArgumentNullException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Use_Custom_Error_Message_For_Nullable()
|
||||||
|
{
|
||||||
|
int? value = null;
|
||||||
|
var result = value.ToResult("Custom nullable error");
|
||||||
|
Assert.That(result.Exception.Message, Does.Contain("Custom nullable error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Should_Support_Complex_Chaining_With_Multiple_Operations()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(10)
|
||||||
|
.Map(x => x * 2)
|
||||||
|
.Bind(x => Result<int>.Succeed(x + 5))
|
||||||
|
.Ensure(x => x > 20, "Value must be greater than 20")
|
||||||
|
.OnSuccess(x => Console.WriteLine($"Success: {x}"))
|
||||||
|
.OnFailure(ex => Console.WriteLine($"Error: {ex.Message}"));
|
||||||
|
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(25));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Should_Support_OnSuccess_And_OnFailure_Chaining()
|
||||||
|
{
|
||||||
|
var successCount = 0;
|
||||||
|
var failureCount = 0;
|
||||||
|
|
||||||
|
Result<int>.Succeed(42)
|
||||||
|
.OnSuccess(_ => successCount++)
|
||||||
|
.OnFailure(_ => failureCount++);
|
||||||
|
|
||||||
|
Assert.That(successCount, Is.EqualTo(1));
|
||||||
|
Assert.That(failureCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Should_Support_Ensure_Chaining_With_Multiple_Conditions()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(50)
|
||||||
|
.Ensure(x => x > 0, "Must be positive")
|
||||||
|
.Ensure(x => x < 100, "Must be less than 100")
|
||||||
|
.Ensure(x => x % 2 == 0, "Must be even");
|
||||||
|
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
603
GFramework.Core.Tests/functional/ResultTTests.cs
Normal file
603
GFramework.Core.Tests/functional/ResultTTests.cs
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
// Copyright (c) 2025 GeWuYou
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using GFramework.Core.Functional;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.Functional;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result<A> 泛型类型测试类
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ResultTTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Constructor_WithValue_Should_Create_Success_Result()
|
||||||
|
{
|
||||||
|
var result = new Result<int>(42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.IsFaulted, Is.False);
|
||||||
|
Assert.That(result.IsBottom, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Constructor_WithException_Should_Create_Faulted_Result()
|
||||||
|
{
|
||||||
|
var exception = new InvalidOperationException("Test error");
|
||||||
|
var result = new Result<int>(exception);
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.IsSuccess, Is.False);
|
||||||
|
Assert.That(result.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Constructor_WithNullException_Should_Throw_ArgumentNullException()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new Result<int>(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DefaultConstructor_Should_Create_Bottom_State()
|
||||||
|
{
|
||||||
|
var result = new Result<int>();
|
||||||
|
Assert.That(result.IsBottom, Is.True);
|
||||||
|
Assert.That(result.IsSuccess, Is.False);
|
||||||
|
Assert.That(result.IsFaulted, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ImplicitConversion_Should_Create_Success_Result()
|
||||||
|
{
|
||||||
|
Result<int> result = 42;
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsSuccess_Should_Return_True_When_Result_Is_Successful()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsFaulted_Should_Return_True_When_Result_Is_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IsBottom_Should_Return_True_When_Result_Is_Bottom()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Bottom;
|
||||||
|
Assert.That(result.IsBottom, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Exception_Should_Return_Exception_When_Faulted()
|
||||||
|
{
|
||||||
|
var exception = new InvalidOperationException("Test");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
Assert.That(result.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Exception_Should_Return_InvalidOperationException_When_Bottom()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Bottom;
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Exception_Should_Return_InvalidOperationException_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Succeed_Should_Create_Successful_Result()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Success_Should_Create_Successful_Result()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Success(42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Fail_Should_Create_Faulted_Result()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithException_Should_Create_Faulted_Result()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Failure(exception);
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithMessage_Should_Create_Faulted_Result()
|
||||||
|
{
|
||||||
|
var message = "Test error";
|
||||||
|
var result = Result<int>.Failure(message);
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception.Message, Is.EqualTo(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Try_Should_Return_Success_When_Function_Succeeds()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Try(() => 42);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Try_Should_Return_Failure_When_Function_Throws()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Try(() => throw new InvalidOperationException("Error"));
|
||||||
|
Assert.That(result.IsFaulted, Is.True);
|
||||||
|
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfFail_WithDefaultValue_Should_Return_Value_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var value = result.IfFail(0);
|
||||||
|
Assert.That(value, Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfFail_WithDefaultValue_Should_Return_Default_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var value = result.IfFail(99);
|
||||||
|
Assert.That(value, Is.EqualTo(99));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfFail_WithFunction_Should_Return_Value_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var value = result.IfFail(_ => 0);
|
||||||
|
Assert.That(value, Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfFail_WithFunction_Should_Execute_Function_When_Faulted()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var executed = false;
|
||||||
|
var value = result.IfFail(ex =>
|
||||||
|
{
|
||||||
|
executed = true;
|
||||||
|
return 99;
|
||||||
|
});
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
Assert.That(value, Is.EqualTo(99));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfFail_WithAction_Should_Execute_Action_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var executed = false;
|
||||||
|
result.IfFail(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfSucc_Should_Execute_Action_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var executed = false;
|
||||||
|
result.IfSucc(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void IfSucc_Should_Not_Execute_Action_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var executed = false;
|
||||||
|
result.IfSucc(_ => executed = true);
|
||||||
|
Assert.That(executed, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Transform_Value_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var mapped = result.Map(x => x.ToString());
|
||||||
|
Assert.That(mapped.IsSuccess, Is.True);
|
||||||
|
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Propagate_Exception_When_Faulted()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var mapped = result.Map(x => x.ToString());
|
||||||
|
Assert.That(mapped.IsFaulted, Is.True);
|
||||||
|
Assert.That(mapped.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Handle_Null_Result_From_Mapper()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var mapped = result.Map<string>(_ => null!);
|
||||||
|
Assert.That(mapped.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_Should_Not_Throw_When_Mapper_Returns_Null()
|
||||||
|
{
|
||||||
|
var result = Result<string>.Succeed("test");
|
||||||
|
Assert.DoesNotThrow(() => result.Map<string?>(_ => null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Chain_Success_Results()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
|
||||||
|
Assert.That(bound.IsSuccess, Is.True);
|
||||||
|
Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Propagate_First_Failure()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
|
||||||
|
Assert.That(bound.IsFaulted, Is.True);
|
||||||
|
Assert.That(bound.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Propagate_Second_Failure()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var exception = new Exception("Bind error");
|
||||||
|
var bound = result.Bind(x => Result<string>.Fail(exception));
|
||||||
|
Assert.That(bound.IsFaulted, Is.True);
|
||||||
|
Assert.That(bound.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bind_Should_Handle_Complex_Chaining()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(10)
|
||||||
|
.Bind(x => Result<int>.Succeed(x * 2))
|
||||||
|
.Bind(x => Result<int>.Succeed(x + 5));
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(25));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task MapAsync_Should_Transform_Value_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var mapped = await result.MapAsync(async x =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1);
|
||||||
|
return x.ToString();
|
||||||
|
});
|
||||||
|
Assert.That(mapped.IsSuccess, Is.True);
|
||||||
|
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task MapAsync_Should_Propagate_Exception_When_Faulted()
|
||||||
|
{
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result<int>.Fail(exception);
|
||||||
|
var mapped = await result.MapAsync(async x =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1);
|
||||||
|
return x.ToString();
|
||||||
|
});
|
||||||
|
Assert.That(mapped.IsFaulted, Is.True);
|
||||||
|
Assert.That(mapped.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task MapAsync_Should_Handle_Async_Exceptions()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var mapped = await result.MapAsync<string>(async _ =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1);
|
||||||
|
throw new InvalidOperationException("Async error");
|
||||||
|
});
|
||||||
|
Assert.That(mapped.IsFaulted, Is.True);
|
||||||
|
Assert.That(mapped.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Match_WithFunctions_Should_Execute_Succ_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var value = result.Match(succ: x => x * 2, fail: _ => 0);
|
||||||
|
Assert.That(value, Is.EqualTo(84));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Match_WithFunctions_Should_Execute_Fail_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var value = result.Match(succ: x => x, fail: _ => 99);
|
||||||
|
Assert.That(value, Is.EqualTo(99));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Match_WithActions_Should_Execute_Succ_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
var executed = false;
|
||||||
|
result.Match(succ: _ => executed = true, fail: _ => { });
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Match_WithActions_Should_Execute_Fail_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var executed = false;
|
||||||
|
result.Match(succ: _ => { }, fail: _ => executed = true);
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Match_Should_Handle_Bottom_State_Correctly()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Bottom;
|
||||||
|
var value = result.Match(succ: x => x, fail: _ => 99);
|
||||||
|
Assert.That(value, Is.EqualTo(99));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_True_When_Both_Success_With_Same_Value()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_Success_Values_Differ()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Succeed(43);
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_True_When_Both_Faulted_With_Same_Exception()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Fail(new InvalidOperationException("Error"));
|
||||||
|
var result2 = Result<int>.Fail(new InvalidOperationException("Error"));
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_Exception_Types_Differ()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Fail(new InvalidOperationException("Error"));
|
||||||
|
var result2 = Result<int>.Fail(new ArgumentException("Error"));
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_True_When_Both_Bottom()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Bottom;
|
||||||
|
var result2 = new Result<int>();
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_States_Differ()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Fail(new Exception("Error"));
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetHashCode_Should_Be_Consistent_For_Equal_Results()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result1.GetHashCode(), Is.EqualTo(result2.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorEquals_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Succeed(42);
|
||||||
|
var result3 = Result<int>.Succeed(43);
|
||||||
|
Assert.That(result1 == result2, Is.True);
|
||||||
|
Assert.That(result1 == result3, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorNotEquals_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(42);
|
||||||
|
var result2 = Result<int>.Succeed(43);
|
||||||
|
Assert.That(result1 != result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Order_Bottom_Before_Faulted()
|
||||||
|
{
|
||||||
|
var bottom = Result<int>.Bottom;
|
||||||
|
var faulted = Result<int>.Fail(new Exception("Error"));
|
||||||
|
Assert.That(bottom.CompareTo(faulted), Is.LessThan(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Order_Faulted_Before_Success()
|
||||||
|
{
|
||||||
|
var faulted = Result<int>.Fail(new Exception("Error"));
|
||||||
|
var success = Result<int>.Succeed(42);
|
||||||
|
Assert.That(faulted.CompareTo(success), Is.LessThan(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Compare_Success_Values_When_Both_Success()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(10);
|
||||||
|
var result2 = Result<int>.Succeed(20);
|
||||||
|
Assert.That(result1.CompareTo(result2), Is.LessThan(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Return_Zero_When_Both_Faulted()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Fail(new Exception("Error1"));
|
||||||
|
var result2 = Result<int>.Fail(new Exception("Error2"));
|
||||||
|
Assert.That(result1.CompareTo(result2), Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Return_Zero_When_Both_Bottom()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Bottom;
|
||||||
|
var result2 = new Result<int>();
|
||||||
|
Assert.That(result1.CompareTo(result2), Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorLessThan_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(10);
|
||||||
|
var result2 = Result<int>.Succeed(20);
|
||||||
|
Assert.That(result1 < result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorLessThanOrEqual_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(10);
|
||||||
|
var result2 = Result<int>.Succeed(10);
|
||||||
|
Assert.That(result1 <= result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorGreaterThan_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(20);
|
||||||
|
var result2 = Result<int>.Succeed(10);
|
||||||
|
Assert.That(result1 > result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OperatorGreaterThanOrEqual_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<int>.Succeed(20);
|
||||||
|
var result2 = Result<int>.Succeed(20);
|
||||||
|
Assert.That(result1 >= result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CompareTo_Should_Handle_NonComparable_Types_Gracefully()
|
||||||
|
{
|
||||||
|
var result1 = Result<object>.Succeed(new object());
|
||||||
|
var result2 = Result<object>.Succeed(new object());
|
||||||
|
Assert.DoesNotThrow(() => result1.CompareTo(result2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToString_Should_Return_Value_String_When_Success()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Succeed(42);
|
||||||
|
Assert.That(result.ToString(), Is.EqualTo("42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToString_Should_Return_Fail_Message_When_Faulted()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Fail(new Exception("Test error"));
|
||||||
|
Assert.That(result.ToString(), Is.EqualTo("Fail(Test error)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ToString_Should_Return_Bottom_When_Bottom()
|
||||||
|
{
|
||||||
|
var result = Result<int>.Bottom;
|
||||||
|
Assert.That(result.ToString(), Is.EqualTo("(Bottom)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Success_WithNullValue_Should_Create_Valid_Result()
|
||||||
|
{
|
||||||
|
var result = Result<string?>.Succeed(null);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Map_WithNullValue_Should_Handle_Correctly()
|
||||||
|
{
|
||||||
|
var result = Result<string?>.Succeed(null);
|
||||||
|
var mapped = result.Map(x => x?.Length ?? 0);
|
||||||
|
Assert.That(mapped.IsSuccess, Is.True);
|
||||||
|
Assert.That(mapped.Match(succ: v => v, fail: _ => -1), Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Equals_WithNullValues_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
var result1 = Result<string?>.Succeed(null);
|
||||||
|
var result2 = Result<string?>.Succeed(null);
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ImplicitConversion_WithNull_Should_Create_Success_With_Null()
|
||||||
|
{
|
||||||
|
Result<string?> result = null;
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Bottom_Should_Be_Readonly_And_Immutable()
|
||||||
|
{
|
||||||
|
var bottom1 = Result<int>.Bottom;
|
||||||
|
var bottom2 = Result<int>.Bottom;
|
||||||
|
Assert.That(bottom1.Equals(bottom2), Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,432 @@
|
|||||||
|
// Copyright (c) 2025 GeWuYou
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using GFramework.Core.Functional;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.Functional;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result 类型测试类
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ResultTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Success 方法应该创建成功结果
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Success_Should_Create_Successful_Result()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var result = Result.Success();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.IsFailure, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Failure 方法使用异常应该创建失败结果
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithException_Should_Create_Failed_Result()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new InvalidOperationException("Test error");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsFailure, Is.True);
|
||||||
|
Assert.That(result.IsSuccess, Is.False);
|
||||||
|
Assert.That(result.Error, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Failure 方法使用消息应该创建带异常的失败结果
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithMessage_Should_Create_Failed_Result_With_Exception()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Test error message";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = Result.Failure(message);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(result.IsFailure, Is.True);
|
||||||
|
Assert.That(result.Error.Message, Is.EqualTo(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Failure 方法使用 null 异常应该抛出 ArgumentNullException
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithNullException_Should_Throw_ArgumentNullException()
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentNullException>(() => Result.Failure((Exception)null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Failure 方法使用 null 或空消息应该抛出 ArgumentException
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Failure_WithNullOrEmptyMessage_Should_Throw_ArgumentException()
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentException>(() => Result.Failure(string.Empty));
|
||||||
|
Assert.Throws<ArgumentException>(() => Result.Failure(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 IsSuccess 属性在结果成功时应该返回 true
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void IsSuccess_Should_Return_True_When_Result_Is_Successful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Success();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 IsFailure 属性在结果失败时应该返回 true
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void IsFailure_Should_Return_True_When_Result_Is_Failed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Failure(new Exception("Error"));
|
||||||
|
|
||||||
|
// Acert
|
||||||
|
Assert.That(result.IsFailure, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Error 属性在结果失败时应该返回异常
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Error_Should_Return_Exception_When_Result_Is_Failed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new InvalidOperationException("Test");
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var error = result.Error;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(error, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Error 属性在结果成功时应该抛出 InvalidOperationException
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Error_Should_Throw_InvalidOperationException_When_Result_Is_Successful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Success();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() => _ = result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Match 方法在结果成功时应该执行 onSuccess 委托
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Match_Should_Execute_OnSuccess_When_Result_Is_Successful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Success();
|
||||||
|
var executed = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var value = result.Match(
|
||||||
|
onSuccess: () =>
|
||||||
|
{
|
||||||
|
executed = true;
|
||||||
|
return "success";
|
||||||
|
},
|
||||||
|
onFailure: _ => "failure"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
Assert.That(value, Is.EqualTo("success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Match 方法在结果失败时应该执行 onFailure 委托
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Match_Should_Execute_OnFailure_WhenFailed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
var executed = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var value = result.Match(
|
||||||
|
onSuccess: () => "success",
|
||||||
|
onFailure: _ =>
|
||||||
|
{
|
||||||
|
executed = true;
|
||||||
|
return "failure";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(executed, Is.True);
|
||||||
|
Assert.That(value, Is.EqualTo("failure"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Match 方法应该将异常传递给 onFailure 处理器
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Match_Should_Pass_Exception_To_OnFailure_Handler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new InvalidOperationException("Test error");
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
Exception? capturedEx = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
result.Match(
|
||||||
|
onSuccess: () => 0,
|
||||||
|
onFailure: ex =>
|
||||||
|
{
|
||||||
|
capturedEx = ex;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(capturedEx, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 ToResult 方法应该将成功结果转换为泛型成功结果
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Convert_Success_To_Generic_Success()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Success();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var genericResult = result.ToResult(42);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(genericResult.IsSuccess, Is.True);
|
||||||
|
Assert.That(genericResult.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 ToResult 方法应该将失败结果转换为泛型失败结果
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Convert_Failure_To_Generic_Failure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception("Error");
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var genericResult = result.ToResult(42);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(genericResult.IsFaulted, Is.True);
|
||||||
|
Assert.That(genericResult.Exception, Is.SameAs(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 ToResult 方法应该保留异常信息
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ToResult_Should_Preserve_Exception_Information()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new InvalidOperationException("Original error");
|
||||||
|
var result = Result.Failure(exception);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var genericResult = result.ToResult("value");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(genericResult.Exception.Message, Is.EqualTo("Original error"));
|
||||||
|
Assert.That(genericResult.Exception, Is.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Equals 方法在两个结果都成功时应该返回 true
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_True_When_Both_Are_Successful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Success();
|
||||||
|
var result2 = Result.Success();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
Assert.That(result1 == result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Equals 方法在两个结果都失败且异常类型和消息相同时应该返回 true
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_True_When_Both_Failed_With_Same_Exception_Type_And_Message()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Failure(new InvalidOperationException("Error"));
|
||||||
|
var result2 = Result.Failure(new InvalidOperationException("Error"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1.Equals(result2), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Equals 方法在状态不同时应该返回 false
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_States_Differ()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Success();
|
||||||
|
var result2 = Result.Failure(new Exception("Error"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
Assert.That(result1 != result2, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Equals 方法在异常类型不同时应该返回 false
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_Exception_Types_Differ()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Failure(new InvalidOperationException("Error"));
|
||||||
|
var result2 = Result.Failure(new ArgumentException("Error"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 Equals 方法在异常消息不同时应该返回 false
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Equals_Should_Return_False_When_Exception_Messages_Differ()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Failure(new Exception("Error1"));
|
||||||
|
var result2 = Result.Failure(new Exception("Error2"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1.Equals(result2), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 GetHashCode 方法对于相等的结果应该返回一致的哈希码
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void GetHashCode_Should_Be_Consistent_For_Equal_Results()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Success();
|
||||||
|
var result2 = Result.Success();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var hash1 = result1.GetHashCode();
|
||||||
|
var hash2 = result2.GetHashCode();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(hash1, Is.EqualTo(hash2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 == 操作符应该正确工作
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void OperatorEquals_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Success();
|
||||||
|
var result2 = Result.Success();
|
||||||
|
var result3 = Result.Failure(new Exception("Error"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1 == result2, Is.True);
|
||||||
|
Assert.That(result1 == result3, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 != 操作符应该正确工作
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void OperatorNotEquals_Should_Work_Correctly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result1 = Result.Success();
|
||||||
|
var result2 = Result.Failure(new Exception("Error"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(result1 != result2, Is.True);
|
||||||
|
Assert.That(result1 != Result.Success(), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 ToString 方法在结果成功时应该返回 "Success"
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ToString_Should_Return_Success_When_Successful()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Success();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var str = result.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(str, Is.EqualTo("Success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 ToString 方法在结果失败时应该返回带消息的 "Fail"
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ToString_Should_Return_Fail_With_Message_When_Failed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var result = Result.Failure(new Exception("Test error"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var str = result.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(str, Is.EqualTo("Fail(Test error)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,7 +45,9 @@ public static class ResultExtensions
|
|||||||
{
|
{
|
||||||
if (result.IsFaulted)
|
if (result.IsFaulted)
|
||||||
return Result<List<T>>.Fail(result.Exception);
|
return Result<List<T>>.Fail(result.Exception);
|
||||||
values.Add(result.Match(succ: v => v, fail: _ => default!));
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
values.Add(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result<List<T>>.Succeed(values);
|
return Result<List<T>>.Succeed(values);
|
||||||
@ -91,6 +93,28 @@ public static class ResultExtensions
|
|||||||
return result.Bind(binder);
|
return result.Bind(binder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步绑定操作,将结果链式转换为异步结果
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var result = Result<int>.Succeed(42);
|
||||||
|
/// var bound = await result.BindAsync(async x =>
|
||||||
|
/// await GetUserAsync(x) is User user
|
||||||
|
/// User>.Succeed(user)
|
||||||
|
/// : Result<User>.Fail(new Exception("User not found")));
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public static async Task<Result<TResult>> BindAsync<T, TResult>(
|
||||||
|
this Result<T> result,
|
||||||
|
Func<T, Task<Result<TResult>>> binder)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(binder);
|
||||||
|
return result.IsSuccess
|
||||||
|
? await binder(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException()))
|
||||||
|
: Result<TResult>.Fail(result.Exception);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 副作用
|
#region 副作用
|
||||||
|
|||||||
@ -27,12 +27,13 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 结果状态枚举,表示结果的不同状态
|
/// 结果状态枚举,表示结果的不同状态
|
||||||
|
/// 排序: Bottom < Faulted < Success
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private enum ResultState : byte
|
private enum ResultState : byte
|
||||||
{
|
{
|
||||||
Bottom,
|
Bottom,
|
||||||
Success,
|
Faulted,
|
||||||
Faulted
|
Success
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ResultState _state;
|
private readonly ResultState _state;
|
||||||
@ -151,8 +152,11 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
|
|||||||
/// <param name="f">映射函数</param>
|
/// <param name="f">映射函数</param>
|
||||||
/// <returns>映射后的结果</returns>
|
/// <returns>映射后的结果</returns>
|
||||||
[Pure]
|
[Pure]
|
||||||
public Result<B> Map<B>(Func<A, B> f) =>
|
public Result<B> Map<B>(Func<A, B> f)
|
||||||
IsSuccess ? new Result<B>(f(_value!)) : new Result<B>(Exception);
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(f);
|
||||||
|
return IsSuccess ? new Result<B>(f(_value!)) : new Result<B>(Exception);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成功时绑定到新 Result,失败时透传异常
|
/// 成功时绑定到新 Result,失败时透传异常
|
||||||
@ -161,8 +165,11 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
|
|||||||
/// <param name="binder">绑定函数</param>
|
/// <param name="binder">绑定函数</param>
|
||||||
/// <returns>绑定后的结果</returns>
|
/// <returns>绑定后的结果</returns>
|
||||||
[Pure]
|
[Pure]
|
||||||
public Result<B> Bind<B>(Func<A, Result<B>> binder) =>
|
public Result<B> Bind<B>(Func<A, Result<B>> binder)
|
||||||
IsSuccess ? binder(_value!) : new Result<B>(Exception);
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(binder);
|
||||||
|
return IsSuccess ? binder(_value!) : new Result<B>(Exception);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步映射
|
/// 异步映射
|
||||||
@ -171,8 +178,20 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
|
|||||||
/// <param name="f">异步映射函数</param>
|
/// <param name="f">异步映射函数</param>
|
||||||
/// <returns>异步映射后的结果</returns>
|
/// <returns>异步映射后的结果</returns>
|
||||||
[Pure]
|
[Pure]
|
||||||
public async Task<Result<B>> MapAsync<B>(Func<A, Task<B>> f) =>
|
public async Task<Result<B>> MapAsync<B>(Func<A, Task<B>> f)
|
||||||
IsSuccess ? new Result<B>(await f(_value!)) : new Result<B>(Exception);
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(f);
|
||||||
|
if (!IsSuccess) return new Result<B>(Exception);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new Result<B>(await f(_value!));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new Result<B>(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ 模式匹配
|
// ------------------------------------------------------------------ 模式匹配
|
||||||
|
|
||||||
@ -303,7 +322,16 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
|
|||||||
// Bottom < Faulted < Success
|
// Bottom < Faulted < Success
|
||||||
if (_state != other._state) return _state.CompareTo(other._state);
|
if (_state != other._state) return _state.CompareTo(other._state);
|
||||||
if (!IsSuccess) return 0;
|
if (!IsSuccess) return 0;
|
||||||
return Comparer<A>.Default.Compare(_value, other._value);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Comparer<A>.Default.Compare(_value, other._value);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// 类型不可比较时返回 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -67,7 +67,11 @@ public readonly struct Result : IEquatable<Result>
|
|||||||
/// <param name="ex">失败的异常</param>
|
/// <param name="ex">失败的异常</param>
|
||||||
/// <returns>失败结果</returns>
|
/// <returns>失败结果</returns>
|
||||||
[Pure]
|
[Pure]
|
||||||
public static Result Failure(Exception ex) => new(false, ex);
|
public static Result Failure(Exception ex)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(ex);
|
||||||
|
return new(false, ex);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据错误消息创建失败结果
|
/// 根据错误消息创建失败结果
|
||||||
@ -75,7 +79,11 @@ public readonly struct Result : IEquatable<Result>
|
|||||||
/// <param name="message">错误消息</param>
|
/// <param name="message">错误消息</param>
|
||||||
/// <returns>失败结果</returns>
|
/// <returns>失败结果</returns>
|
||||||
[Pure]
|
[Pure]
|
||||||
public static Result Failure(string message) => new(false, new Exception(message));
|
public static Result Failure(string message)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(message);
|
||||||
|
return new(false, new Exception(message));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据成功或失败状态分别执行不同的处理逻辑
|
/// 根据成功或失败状态分别执行不同的处理逻辑
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user