From 1d14bd2261f9ed66e9261ffed25028b7c1122b5a Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:03:01 -0500 Subject: [PATCH] refactor: remove static methods from generic type and move to non-generic static class. --- README.md | 43 ++++-- .../.editorconfig | 1 - src/StevanFreeborn.Results/Result.cs | 142 ++++++++++-------- .../ResultAsyncExtensions.cs | 12 +- tests/Directory.Packages.props | 2 +- .../Directory.Build.props | 3 + .../ErrorTests.cs | 9 +- .../ResultAsyncExtensionsTests.cs | 75 ++++++--- .../ResultTTests.cs | 66 +++++--- .../ResultTests.cs | 60 +++++--- 10 files changed, 258 insertions(+), 155 deletions(-) rename src/{StevanFreeborn.Results => }/.editorconfig (76%) create mode 100644 tests/StevanFreeborn.Results.Tests/Directory.Build.props diff --git a/README.md b/README.md index 3151208..39ce4b3 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ dotnet add package StevanFreeborn.Results using StevanFreeborn.Results; // Create a successful result -Result ok = Result.Ok(default); +Result ok = Result.Ok(default); // Create a failed result -Result fail = Result.Fail(new Error("NotFound", "User not found")); +Result fail = Result.Fail(new Error("NotFound", "User not found")); // Work with results that have values Result divisionResult = Divide(10, 2); @@ -69,11 +69,11 @@ Result GetUser(int id) { if (id <= 0) { - return Result.Fail(new DomainError("InvalidId", "User ID must be positive")); + return Result.Fail(new DomainError("InvalidId", "User ID must be positive")); } // ... fetch user - return Result.Ok(user); + return Result.Ok(user); } // Chain with custom errors @@ -122,14 +122,29 @@ Result operation = DoSomething(); ## Result Types +### `Result` (Static Factory Class) + +The static `Result` class provides factory methods for creating result instances: + +```csharp +// Create successful result +Result ok = Result.Ok(42); + +// Create failed result +Result fail = Result.Fail(new Error("Failed", "Something went wrong")); + +// Wrap a function that may throw +Result result = Result.Try(() => File.ReadAllText("file.txt")); +``` + ### `Result` The main Result type with generic type parameters for both value and error: ```csharp // Creation -Result ok = Result.Ok(42); -Result fail = Result.Fail(new Error("Invalid", "Invalid input")); +Result ok = Result.Ok(42); +Result fail = Result.Fail(new Error("Invalid", "Invalid input")); // Check status if (result.IsSuccess) { /* ... */ } @@ -147,8 +162,8 @@ Error error = result.Error; For operations that don't return a value, use `Unit`: ```csharp -Result ok = Result.Ok(default); -Result fail = Result.Fail(new Error("Failed", "Something went wrong")); +Result ok = Result.Ok(default); +Result fail = Result.Fail(new Error("Failed", "Something went wrong")); ``` ## Functional Operations @@ -158,8 +173,8 @@ Result fail = Result.Fail(new Error("Failed", "Somethi Transforms the value if success, propagates the error if failure. ```csharp -Result ok = Result.Ok(5); -Result mapped = ok.Map(x => x.ToString()); // Result.Ok("5") +Result ok = Result.Ok(5); +Result mapped = ok.Map(x => x.ToString()); // Result.Ok("5") ``` ### MapError @@ -167,7 +182,7 @@ Result mapped = ok.Map(x => x.ToString()); // Result fail = Result.Fail(new Error("NotFound", "Not found")); +Result fail = Result.Fail(new Error("NotFound", "Not found")); Result mapped = fail.MapError(e => new Error("Unexpected", e.Message)); ``` @@ -212,10 +227,10 @@ Wraps a function that may throw an exception in a Result. ```csharp // Simple usage with default error handler -Result result = Result.Try(() => File.ReadAllText("file.txt")); +Result result = Result.Try(() => File.ReadAllText("file.txt")); // Custom error handler -Result result = Result.Try( +Result result = Result.Try( () => File.ReadAllText("file.txt"), ex => new Error("ReadError", ex.Message) ); @@ -241,7 +256,7 @@ string result = await result.MatchAsync( ); // Async Try -Result result = await ResultAsyncExtensions.TryAsync( +Result result = await Result.TryAsync( () => HttpClient.GetStringAsync("https://api.example.com") ); ``` diff --git a/src/StevanFreeborn.Results/.editorconfig b/src/.editorconfig similarity index 76% rename from src/StevanFreeborn.Results/.editorconfig rename to src/.editorconfig index f5eafdf..a834286 100644 --- a/src/StevanFreeborn.Results/.editorconfig +++ b/src/.editorconfig @@ -1,6 +1,5 @@ [*.cs] -dotnet_diagnostic.CA1000.severity = none dotnet_diagnostic.CA1031.severity = none dotnet_diagnostic.CA1510.severity = none dotnet_diagnostic.CA2225.severity = none \ No newline at end of file diff --git a/src/StevanFreeborn.Results/Result.cs b/src/StevanFreeborn.Results/Result.cs index bfd3688..d64262f 100644 --- a/src/StevanFreeborn.Results/Result.cs +++ b/src/StevanFreeborn.Results/Result.cs @@ -1,6 +1,79 @@ namespace StevanFreeborn.Results { + /// + /// Static factory methods for creating result instances. + /// + public static class Result + { + /// + /// Creates a successful result with the specified value. + /// + /// The type of the value. + /// The type of the error. + /// The value. + /// A successful . + public static Result Ok(T value) where TError : IError + { + return new Result(true, value, default); + } + + /// + /// Creates a failed result with the specified error. + /// + /// The type of the value. + /// The type of the error. + /// The error. + /// A failed . + public static Result Fail(TError error) where TError : IError + { + return new Result(false, default!, error); + } + + /// + /// Executes the specified function and wraps the result in a . + /// + /// The type of the value. + /// The type of the error. + /// The function to execute. + /// A successful result with the return value if no exception is thrown; otherwise, a failed result. + public static Result Try(Func func) where TError : IError + { + return Try(func, ex => (TError)(IError)new Error("UnexpectedError", ex.Message)); + } + + /// + /// Executes the specified function and wraps the result in a using the specified error handler. + /// + /// The type of the value. + /// The type of the error. + /// The function to execute. + /// The function to convert exceptions to errors. + /// A successful result with the return value if no exception is thrown; otherwise, a failed result. + /// Thrown when func or errorHandler is null. + public static Result Try(Func func, Func errorHandler) where TError : IError + { + if (func is null) + { + throw new ArgumentNullException(nameof(func)); + } + + if (errorHandler is null) + { + throw new ArgumentNullException(nameof(errorHandler)); + } + + try + { + return Ok(func()); + } + catch (Exception ex) + { + return Fail(errorHandler(ex)); + } + } + } + /// /// Represents a result with a value and error type, used for operations that either succeed with a value or fail with an error. /// @@ -42,33 +115,13 @@ namespace StevanFreeborn.Results _error = error; } - /// - /// Creates a successful result with the specified value. - /// - /// The value. - /// A successful . - public static Result Ok(T value) - { - return new Result(true, value, default); - } - - /// - /// Creates a failed result with the specified error. - /// - /// The error. - /// A failed . - public static Result Fail(TError error) - { - return new Result(false, default!, error); - } - /// /// Implicitly converts a value to a successful . /// /// The value to convert. public static implicit operator Result(T value) { - return Ok(value); + return Result.Ok(value); } /// @@ -77,7 +130,7 @@ namespace StevanFreeborn.Results /// The error to convert. public static implicit operator Result(TError error) { - return Fail(error); + return Result.Fail(error); } /// @@ -94,7 +147,7 @@ namespace StevanFreeborn.Results throw new ArgumentNullException(nameof(mapper)); } - return IsSuccess ? Result.Ok(mapper(Value)) : Result.Fail(Error); + return IsSuccess ? Result.Ok(mapper(Value)) : Result.Fail(Error); } /// @@ -110,7 +163,7 @@ namespace StevanFreeborn.Results throw new ArgumentNullException(nameof(mapper)); } - return IsFailure ? Result.Fail(mapper(Error)) : Result.Ok(Value); + return IsFailure ? Result.Fail(mapper(Error)) : Result.Ok(Value); } /// @@ -127,7 +180,7 @@ namespace StevanFreeborn.Results throw new ArgumentNullException(nameof(binder)); } - return IsSuccess ? binder(Value) : Result.Fail(Error); + return IsSuccess ? binder(Value) : Result.Fail(Error); } /// @@ -220,45 +273,6 @@ namespace StevanFreeborn.Results } return this; } - - /// - /// Executes the specified function and wraps the result in a . - /// - /// The function to execute. - /// A successful result with the return value if no exception is thrown; otherwise, a failed result. - public static Result Try(Func func) - { - return Try(func, ex => (TError)(IError)new Error("UnexpectedError", ex.Message)); - } - - /// - /// Executes the specified function and wraps the result in a using the specified error handler. - /// - /// The function to execute. - /// The function to convert exceptions to errors. - /// A successful result with the return value if no exception is thrown; otherwise, a failed result. - /// Thrown when func or errorHandler is null. - public static Result Try(Func func, Func errorHandler) - { - if (func is null) - { - throw new ArgumentNullException(nameof(func)); - } - - if (errorHandler is null) - { - throw new ArgumentNullException(nameof(errorHandler)); - } - - try - { - return Ok(func()); - } - catch (Exception ex) - { - return Fail(errorHandler(ex)); - } - } } } \ No newline at end of file diff --git a/src/StevanFreeborn.Results/ResultAsyncExtensions.cs b/src/StevanFreeborn.Results/ResultAsyncExtensions.cs index 6bc32b5..ee5ed71 100644 --- a/src/StevanFreeborn.Results/ResultAsyncExtensions.cs +++ b/src/StevanFreeborn.Results/ResultAsyncExtensions.cs @@ -56,7 +56,7 @@ namespace StevanFreeborn.Results throw new ArgumentNullException(nameof(mapper)); } - return result.IsFailure ? await mapper(result.Error).ConfigureAwait(false) : Result.Ok(result.Value); + return result.IsFailure ? await mapper(result.Error).ConfigureAwait(false) : Result.Ok(result.Value); } /// @@ -177,11 +177,11 @@ namespace StevanFreeborn.Results try { await action().ConfigureAwait(false); - return Result.Ok(default); + return Result.Ok(default); } catch (Exception ex) { - return Result.Fail(errorHandler(ex)); + return Result.Fail(errorHandler(ex)); } } @@ -208,7 +208,7 @@ namespace StevanFreeborn.Results throw new ArgumentNullException(nameof(binder)); } - return result.IsSuccess ? await binder(result.Value).ConfigureAwait(false) : Result.Fail(result.Error); + return result.IsSuccess ? await binder(result.Value).ConfigureAwait(false) : Result.Fail(result.Error); } /// @@ -278,11 +278,11 @@ namespace StevanFreeborn.Results try { - return Result.Ok(await func().ConfigureAwait(false)); + return Result.Ok(await func().ConfigureAwait(false)); } catch (Exception ex) { - return Result.Fail(errorHandler(ex)); + return Result.Fail(errorHandler(ex)); } } } diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index cf8b59b..132da3e 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -3,6 +3,6 @@ true - + diff --git a/tests/StevanFreeborn.Results.Tests/Directory.Build.props b/tests/StevanFreeborn.Results.Tests/Directory.Build.props new file mode 100644 index 0000000..12d9ad0 --- /dev/null +++ b/tests/StevanFreeborn.Results.Tests/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/tests/StevanFreeborn.Results.Tests/ErrorTests.cs b/tests/StevanFreeborn.Results.Tests/ErrorTests.cs index f6c2368..132d790 100644 --- a/tests/StevanFreeborn.Results.Tests/ErrorTests.cs +++ b/tests/StevanFreeborn.Results.Tests/ErrorTests.cs @@ -6,6 +6,7 @@ public class ErrorTests public async Task Constructor_WhenCalled_ItShouldSetCode() { var error = new Error("code", "message"); + await Assert.That(error.Code).IsEqualTo("code"); } @@ -13,6 +14,7 @@ public class ErrorTests public async Task Constructor_WhenCalled_ItShouldSetMessage() { var error = new Error("code", "message"); + await Assert.That(error.Message).IsEqualTo("message"); } @@ -21,6 +23,7 @@ public class ErrorTests { var metadata = new Dictionary { { "key", "value" } }; var error = new Error("code", "message", metadata); + await Assert.That(error.Metadata!.Count).IsEqualTo(1); await Assert.That(error.Metadata["key"]).IsEqualTo("value"); } @@ -29,7 +32,8 @@ public class ErrorTests public async Task Constructor_WhenCalledWithNullMetadata_ItShouldSetMetadataToNull() { var error = new Error("code", "message", null); - await Assert.That(error.Metadata!).IsNull(); + + await Assert.That(error.Metadata).IsNull(); } [Test] @@ -37,6 +41,7 @@ public class ErrorTests { var error1 = new Error("code", "message"); var error2 = new Error("code", "message"); + await Assert.That(error1).IsEqualTo(error2); } @@ -45,6 +50,7 @@ public class ErrorTests { var error1 = new Error("code1", "message"); var error2 = new Error("code2", "message"); + await Assert.That(error1 == error2).IsFalse(); } @@ -52,6 +58,7 @@ public class ErrorTests public async Task Error_WhenCreated_ItShouldImplementIError() { var error = new Error("code", "message"); + await Assert.That(error).IsAssignableTo(); } } diff --git a/tests/StevanFreeborn.Results.Tests/ResultAsyncExtensionsTests.cs b/tests/StevanFreeborn.Results.Tests/ResultAsyncExtensionsTests.cs index 6b821a3..91621b9 100644 --- a/tests/StevanFreeborn.Results.Tests/ResultAsyncExtensionsTests.cs +++ b/tests/StevanFreeborn.Results.Tests/ResultAsyncExtensionsTests.cs @@ -7,42 +7,49 @@ public class ResultAsyncExtensionsTests [Test] public async Task MapAsync_WhenResultIsSuccess_ItShouldInvokeAsyncAction() { - var result = Result.Ok(default); + var result = Result.Ok(default); var invoked = false; + await result.MapAsync(_ => { invoked = true; return Task.CompletedTask; }); + await Assert.That(invoked).IsTrue(); } [Test] public async Task MapAsync_WhenResultIsFailure_ItShouldSkipAsyncAction() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var invoked = false; + await result.MapAsync(_ => { invoked = true; return Task.CompletedTask; }); + await Assert.That(invoked).IsFalse(); } [Test] public async Task MapAsync_WhenCalled_ItShouldReturnOriginalResult() { - var result = Result.Ok(default); + var result = Result.Ok(default); var mapped = await result.MapAsync(_ => Task.CompletedTask); + await Assert.That(mapped.IsSuccess).IsTrue(); } [Test] public async Task BindAsync_WhenResultIsSuccess_ItShouldInvokeBinderAndReturnResult() { - var result = Result.Ok(default); - var bound = await result.BindAsync(_ => Task.FromResult(Result.Fail(new Error("new_error", "new message")))); + var result = Result.Ok(default); + var bound = await result.BindAsync(_ => Task.FromResult(Result.Fail(new Error("new_error", "new message")))); + await Assert.That(bound.IsFailure).IsTrue(); } [Test] public async Task BindAsync_WhenResultIsFailure_ItShouldReturnOriginalResult() { - var result = Result.Fail(new Error("code", "message")); - var bound = await result.BindAsync(_ => Task.FromResult(Result.Ok(default))); + var result = Result.Fail(new Error("code", "message")); + var bound = await result.BindAsync(_ => Task.FromResult(Result.Ok(default))); + await Assert.That(bound.IsFailure).IsTrue(); await Assert.That(bound.Error.Code).IsEqualTo("code"); } @@ -50,16 +57,18 @@ public class ResultAsyncExtensionsTests [Test] public async Task MatchAsync_WhenResultIsSuccess_ItShouldReturnOnSuccessValue() { - var result = Result.Ok(default); + var result = Result.Ok(default); var matched = await result.MatchAsync(_ => Task.FromResult("success"), _ => Task.FromResult("failure")); + await Assert.That(matched).IsEqualTo("success"); } [Test] public async Task MatchAsync_WhenResultIsFailure_ItShouldReturnOnFailureValue() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var matched = await result.MatchAsync(_ => Task.FromResult("success"), e => Task.FromResult(e.Code)); + await Assert.That(matched).IsEqualTo("code"); } @@ -67,6 +76,7 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenActionSucceeds_ItShouldReturnOk() { var result = await ResultAsyncExtensions.TryAsync(() => Task.CompletedTask); + await Assert.That(result.IsSuccess).IsTrue(); } @@ -74,6 +84,7 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenActionThrowsException_ItShouldReturnFailWithUnexpectedError() { var result = await ResultAsyncExtensions.TryAsync(() => throw new InvalidOperationException("test error")); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError"); } @@ -82,8 +93,10 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenActionThrowsException_ItShouldUseCustomError() { var result = await ResultAsyncExtensions.TryAsync( - () => throw new InvalidOperationException("test"), - ex => new Error("custom", ex.Message)); + () => throw new InvalidOperationException("test"), + ex => new Error("custom", ex.Message) + ); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("custom"); } @@ -91,8 +104,9 @@ public class ResultAsyncExtensionsTests [Test] public async Task MapAsync_WhenResultIsSuccess_ItShouldTransformValue() { - var result = Result.Ok(5); + var result = Result.Ok(5); var mapped = await result.MapAsync(async n => (await Task.FromResult(n)).ToString(CultureInfo.InvariantCulture)); + await Assert.That(mapped.IsSuccess).IsTrue(); await Assert.That(mapped.Value).IsEqualTo("5"); } @@ -101,8 +115,9 @@ public class ResultAsyncExtensionsTests public async Task MapAsync_WhenResultIsFailure_ItShouldPropagateError() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); var mapped = await result.MapAsync(async n => (await Task.FromResult(n)).ToString(CultureInfo.InvariantCulture)); + await Assert.That(mapped.IsFailure).IsTrue(); await Assert.That(mapped.Error).IsEqualTo(error); } @@ -110,8 +125,9 @@ public class ResultAsyncExtensionsTests [Test] public async Task BindAsync_WhenResultIsSuccess_ItShouldChainToBinderResult() { - var result = Result.Ok(5); - var bound = await result.BindAsync(async n => await Task.FromResult(Result.Ok(n.ToString(CultureInfo.InvariantCulture)))); + var result = Result.Ok(5); + var bound = await result.BindAsync(async n => await Task.FromResult(Result.Ok(n.ToString(CultureInfo.InvariantCulture)))); + await Assert.That(bound.IsSuccess).IsTrue(); await Assert.That(bound.Value).IsEqualTo("5"); } @@ -120,8 +136,9 @@ public class ResultAsyncExtensionsTests public async Task BindAsync_WhenResultIsFailure_ItShouldPropagateError() { var error = new Error("code", "message"); - var result = Result.Fail(error); - var bound = await result.BindAsync(async n => await Task.FromResult(Result.Ok(n.ToString(CultureInfo.InvariantCulture)))); + var result = Result.Fail(error); + var bound = await result.BindAsync(async n => await Task.FromResult(Result.Ok(n.ToString(CultureInfo.InvariantCulture)))); + await Assert.That(bound.IsFailure).IsTrue(); await Assert.That(bound.Error).IsEqualTo(error); } @@ -129,20 +146,24 @@ public class ResultAsyncExtensionsTests [Test] public async Task MatchAsyncGeneric_WhenResultIsSuccess_ItShouldReturnOnSuccessValue() { - var result = Result.Ok("test"); + var result = Result.Ok("test"); var matched = await result.MatchAsync( - v => Task.FromResult(v.Length), - _ => Task.FromResult(0)); + v => Task.FromResult(v.Length), + _ => Task.FromResult(0) + ); + await Assert.That(matched).IsEqualTo(4); } [Test] public async Task MatchAsyncGeneric_WhenResultIsFailure_ItShouldReturnOnFailureValue() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var matched = await result.MatchAsync( - v => Task.FromResult(v.Length), - _ => Task.FromResult(-1)); + v => Task.FromResult(v.Length), + _ => Task.FromResult(-1) + ); + await Assert.That(matched).IsEqualTo(-1); } @@ -150,6 +171,7 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenFuncSucceeds_ItShouldReturnOkWithValue() { var result = await ResultAsyncExtensions.TryAsync(async () => await Task.FromResult(42)); + await Assert.That(result.IsSuccess).IsTrue(); await Assert.That(result.Value).IsEqualTo(42); } @@ -158,6 +180,7 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenFuncThrowsException_ItShouldReturnFailWithUnexpectedError() { var result = await ResultAsyncExtensions.TryAsync(() => throw new InvalidOperationException("test error")); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError"); } @@ -166,8 +189,10 @@ public class ResultAsyncExtensionsTests public async Task TryAsync_WhenFuncThrowsException_ItShouldUseCustomError() { var result = await ResultAsyncExtensions.TryAsync( - () => throw new InvalidOperationException("test"), - ex => new Error("custom", ex.Message)); + () => throw new InvalidOperationException("test"), + ex => new Error("custom", ex.Message) + ); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("custom"); } diff --git a/tests/StevanFreeborn.Results.Tests/ResultTTests.cs b/tests/StevanFreeborn.Results.Tests/ResultTTests.cs index 35e846f..b3b5ec9 100644 --- a/tests/StevanFreeborn.Results.Tests/ResultTTests.cs +++ b/tests/StevanFreeborn.Results.Tests/ResultTTests.cs @@ -7,7 +7,8 @@ public class ResultTTests [Test] public async Task Ok_WhenCalledWithValue_ItShouldReturnSuccessResult() { - var result = Result.Ok("test"); + var result = Result.Ok("test"); + await Assert.That(result.IsSuccess).IsTrue(); await Assert.That(result.IsFailure).IsFalse(); await Assert.That(result.Value).IsEqualTo("test"); @@ -17,24 +18,25 @@ public class ResultTTests public async Task Fail_WhenCalled_ItShouldReturnFailureResult() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.IsSuccess).IsFalse(); await Assert.That(result.Error).IsEqualTo(error); } [Test] - public async Task Value_WhenResultIsFailure_ItShouldThrowInvalidOperationException() + public void Value_WhenResultIsFailure_ItShouldThrowInvalidOperationException() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); Assert.Throws(() => _ = result.Value); } [Test] - public async Task Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException() + public void Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException() { - var result = Result.Ok("test"); + var result = Result.Ok("test"); Assert.Throws(() => _ = result.Error); } @@ -43,6 +45,7 @@ public class ResultTTests public async Task ImplicitConversion_FromValue_ItShouldReturnSuccessResult() { Result result = "test"; + await Assert.That(result.IsSuccess).IsTrue(); await Assert.That(result.Value).IsEqualTo("test"); } @@ -52,6 +55,7 @@ public class ResultTTests { var error = new Error("code", "message"); Result result = error; + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error).IsEqualTo(error); } @@ -59,8 +63,9 @@ public class ResultTTests [Test] public async Task Map_WhenResultIsSuccess_ItShouldTransformValue() { - var result = Result.Ok(5); + var result = Result.Ok(5); var mapped = result.Map(n => n.ToString(CultureInfo.InvariantCulture)); + await Assert.That(mapped.IsSuccess).IsTrue(); await Assert.That(mapped.Value).IsEqualTo("5"); } @@ -69,8 +74,9 @@ public class ResultTTests public async Task Map_WhenResultIsFailure_ItShouldPropagateError() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); var mapped = result.Map(n => n.ToString(CultureInfo.InvariantCulture)); + await Assert.That(mapped.IsFailure).IsTrue(); await Assert.That(mapped.Error).IsEqualTo(error); } @@ -79,8 +85,9 @@ public class ResultTTests public async Task MapError_WhenResultIsFailure_ItShouldTransformError() { var originalError = new Error("original", "original message"); - var result = Result.Fail(originalError); + var result = Result.Fail(originalError); var transformed = result.MapError(e => new Error("transformed", e.Message)); + await Assert.That(transformed.IsFailure).IsTrue(); await Assert.That(transformed.Error.Code).IsEqualTo("transformed"); } @@ -88,8 +95,9 @@ public class ResultTTests [Test] public async Task MapError_WhenResultIsSuccess_ItShouldReturnUnchanged() { - var result = Result.Ok(5); + var result = Result.Ok(5); var mapped = result.MapError(e => new Error("transformed", e.Message)); + await Assert.That(mapped.IsSuccess).IsTrue(); await Assert.That(mapped.Value).IsEqualTo(5); } @@ -97,8 +105,9 @@ public class ResultTTests [Test] public async Task Bind_WhenResultIsSuccess_ItShouldChainToBinderResult() { - var result = Result.Ok(5); - var bound = result.Bind(n => Result.Ok(n.ToString(CultureInfo.InvariantCulture))); + var result = Result.Ok(5); + var bound = result.Bind(n => Result.Ok(n.ToString(CultureInfo.InvariantCulture))); + await Assert.That(bound.IsSuccess).IsTrue(); await Assert.That(bound.Value).IsEqualTo("5"); } @@ -107,8 +116,9 @@ public class ResultTTests public async Task Bind_WhenResultIsFailure_ItShouldPropagateError() { var error = new Error("code", "message"); - var result = Result.Fail(error); - var bound = result.Bind(n => Result.Ok(n.ToString(CultureInfo.InvariantCulture))); + var result = Result.Fail(error); + var bound = result.Bind(n => Result.Ok(n.ToString(CultureInfo.InvariantCulture))); + await Assert.That(bound.IsFailure).IsTrue(); await Assert.That(bound.Error).IsEqualTo(error); } @@ -116,8 +126,9 @@ public class ResultTTests [Test] public async Task Bind_WhenResultIsSuccess_ItShouldReturnBinderFailureResult() { - var result = Result.Ok(5); - var bound = result.Bind(_ => Result.Fail(new Error("bind_error", "bound failed"))); + var result = Result.Ok(5); + var bound = result.Bind(_ => Result.Fail(new Error("bind_error", "bound failed"))); + await Assert.That(bound.IsFailure).IsTrue(); await Assert.That(bound.Error.Code).IsEqualTo("bind_error"); } @@ -125,25 +136,29 @@ public class ResultTTests [Test] public async Task Match_WhenResultIsSuccess_ItShouldReturnOnSuccessValue() { - var result = Result.Ok("test"); + var result = Result.Ok("test"); var matched = result.Match(v => v.Length, _ => 0); + await Assert.That(matched).IsEqualTo(4); } [Test] public async Task Match_WhenResultIsFailure_ItShouldReturnOnFailureValue() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var matched = result.Match(v => v.Length, e => -1); + await Assert.That(matched).IsEqualTo(-1); } [Test] public async Task Match_WhenResultIsSuccess_ItShouldInvokeOnSuccess() { - var result = Result.Ok("test"); + var result = Result.Ok("test"); var invoked = false; + result.Match(v => { invoked = true; }, _ => { }); + await Assert.That(invoked).IsTrue(); } @@ -151,16 +166,19 @@ public class ResultTTests public async Task Match_WhenResultIsFailure_ItShouldInvokeOnFailure() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); var invoked = false; + result.Match(_ => { }, e => { invoked = true; }); + await Assert.That(invoked).IsTrue(); } [Test] public async Task Try_WhenFuncSucceeds_ItShouldReturnOkWithValue() { - var result = Result.Try(() => 42); + var result = Result.Try(() => 42); + await Assert.That(result.IsSuccess).IsTrue(); await Assert.That(result.Value).IsEqualTo(42); } @@ -168,7 +186,8 @@ public class ResultTTests [Test] public async Task Try_WhenFuncThrowsException_ItShouldReturnFailWithUnexpectedError() { - var result = Result.Try(() => throw new InvalidOperationException("test error")); + var result = Result.Try(() => throw new InvalidOperationException("test error")); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError"); } @@ -176,7 +195,8 @@ public class ResultTTests [Test] public async Task Try_WhenFuncThrowsException_ItShouldUseCustomError() { - var result = Result.Try(() => throw new InvalidOperationException("test"), ex => new Error("custom", ex.Message)); + var result = Result.Try(() => throw new InvalidOperationException("test"), ex => new Error("custom", ex.Message)); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("custom"); } diff --git a/tests/StevanFreeborn.Results.Tests/ResultTests.cs b/tests/StevanFreeborn.Results.Tests/ResultTests.cs index 13a0f00..93a2396 100644 --- a/tests/StevanFreeborn.Results.Tests/ResultTests.cs +++ b/tests/StevanFreeborn.Results.Tests/ResultTests.cs @@ -5,7 +5,8 @@ public class ResultTests [Test] public async Task Ok_WhenCalled_ItShouldReturnSuccessResult() { - var result = Result.Ok(default); + var result = Result.Ok(default); + await Assert.That(result.IsSuccess).IsTrue(); await Assert.That(result.IsFailure).IsFalse(); } @@ -14,16 +15,17 @@ public class ResultTests public async Task Fail_WhenCalled_ItShouldReturnFailureResult() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); + await Assert.That(result.IsSuccess).IsFalse(); await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error).IsEqualTo(error); } [Test] - public async Task Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException() + public void Error_WhenResultIsSuccess_ItShouldThrowInvalidOperationException() { - Assert.Throws(() => _ = Result.Ok(default).Error); + Assert.Throws(() => _ = Result.Ok(default).Error); } [Test] @@ -31,6 +33,7 @@ public class ResultTests { var error = new Error("code", "message"); Result result = error; + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error).IsEqualTo(error); } @@ -38,42 +41,49 @@ public class ResultTests [Test] public async Task Map_WhenResultIsSuccess_ItShouldInvokeAction() { - var result = Result.Ok(default); + var result = Result.Ok(default); var invoked = false; + result.Map(_ => invoked = true); + await Assert.That(invoked).IsTrue(); } [Test] public async Task Map_WhenResultIsFailure_ItShouldSkipAction() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var invoked = false; + result.Map(_ => invoked = true); + await Assert.That(invoked).IsFalse(); } [Test] public async Task Map_WhenCalled_ItShouldReturnOriginalResult() { - var result = Result.Ok(default); + var result = Result.Ok(default); var mapped = result.Map(_ => { }); + await Assert.That(mapped.IsSuccess).IsTrue(); } [Test] public async Task Bind_WhenResultIsSuccess_ItShouldInvokeBinder() { - var result = Result.Ok(default); - var bound = result.Bind(_ => Result.Ok(default)); + var result = Result.Ok(default); + var bound = result.Bind(_ => Result.Ok(default)); + await Assert.That(bound.IsSuccess).IsTrue(); } [Test] public async Task Bind_WhenResultIsFailure_ItShouldReturnOriginalResult() { - var result = Result.Fail(new Error("code", "message")); - var bound = result.Bind(_ => Result.Ok(default)); + var result = Result.Fail(new Error("code", "message")); + var bound = result.Bind(_ => Result.Ok(default)); + await Assert.That(bound.IsFailure).IsTrue(); await Assert.That(bound.Error).IsEqualTo(result.Error); } @@ -81,33 +91,38 @@ public class ResultTests [Test] public async Task Bind_WhenResultIsSuccess_ItShouldReturnBinderResult() { - var result = Result.Ok(default); - var bound = result.Bind(_ => Result.Fail(new Error("new_error", "new message"))); + var result = Result.Ok(default); + var bound = result.Bind(_ => Result.Fail(new Error("new_error", "new message"))); + await Assert.That(bound.IsFailure).IsTrue(); } [Test] public async Task Match_WhenResultIsSuccess_ItShouldReturnOnSuccessValue() { - var result = Result.Ok(default); + var result = Result.Ok(default); var matched = result.Match(_ => "success", _ => "failure"); + await Assert.That(matched).IsEqualTo("success"); } [Test] public async Task Match_WhenResultIsFailure_ItShouldReturnOnFailureValue() { - var result = Result.Fail(new Error("code", "message")); + var result = Result.Fail(new Error("code", "message")); var matched = result.Match(_ => "success", e => e.Code); + await Assert.That(matched).IsEqualTo("code"); } [Test] public async Task Match_WhenResultIsSuccess_ItShouldInvokeOnSuccess() { - var result = Result.Ok(default); + var result = Result.Ok(default); var invoked = false; + result.Match(_ => invoked = true, _ => { }); + await Assert.That(invoked).IsTrue(); } @@ -115,23 +130,27 @@ public class ResultTests public async Task Match_WhenResultIsFailure_ItShouldInvokeOnFailure() { var error = new Error("code", "message"); - var result = Result.Fail(error); + var result = Result.Fail(error); var invoked = false; + result.Match(_ => { }, e => invoked = true); + await Assert.That(invoked).IsTrue(); } [Test] public async Task Try_WhenActionSucceeds_ItShouldReturnOk() { - var result = Result.Try(() => default(Unit)); + var result = Result.Try(() => default); + await Assert.That(result.IsSuccess).IsTrue(); } [Test] public async Task Try_WhenActionThrowsException_ItShouldReturnFailWithUnexpectedError() { - var result = Result.Try(() => throw new Exception("test error")); + var result = Result.Try(() => throw new Exception("test error")); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("UnexpectedError"); } @@ -139,7 +158,8 @@ public class ResultTests [Test] public async Task Try_WhenActionThrowsException_ItShouldUseCustomError() { - var result = Result.Try(() => throw new Exception("test"), ex => new Error("custom", ex.Message)); + var result = Result.Try(() => throw new Exception("test"), ex => new Error("custom", ex.Message)); + await Assert.That(result.IsFailure).IsTrue(); await Assert.That(result.Error.Code).IsEqualTo("custom"); }