diff --git a/src/main/java/io/github/joselion/maybe/EffectHandler.java b/src/main/java/io/github/joselion/maybe/EffectHandler.java index b619bdb..f980e7a 100644 --- a/src/main/java/io/github/joselion/maybe/EffectHandler.java +++ b/src/main/java/io/github/joselion/maybe/EffectHandler.java @@ -55,7 +55,7 @@ static EffectHandler ofError(final E error) { * @return the possible thrown exception */ Optional error() { - return error; + return this.error; } /** @@ -65,7 +65,7 @@ Optional error() { * @return the same handler to continue chainning operations */ public EffectHandler doOnSuccess(final Runnable effect) { - if (error.isEmpty()) { + if (this.error.isEmpty()) { effect.run(); } @@ -83,7 +83,8 @@ public EffectHandler doOnSuccess(final Runnable effect) { * @return the same handler to continue chainning operations */ public EffectHandler doOnError(final Class ofType, final Consumer effect) { - error.filter(ofType::isInstance) + this.error + .filter(ofType::isInstance) .map(ofType::cast) .ifPresent(effect); @@ -98,7 +99,7 @@ public EffectHandler doOnError(final Class ofType, f * @return the same handler to continue chainning operations */ public EffectHandler doOnError(final Consumer effect) { - error.ifPresent(effect); + this.error.ifPresent(effect); return this; } @@ -116,7 +117,8 @@ public EffectHandler doOnError(final Consumer effect) { * caught. The same handler instance otherwise */ public EffectHandler catchError(final Class ofType, final Consumer handler) { - return error.filter(ofType::isInstance) + return this.error + .filter(ofType::isInstance) .map(ofType::cast) .map(caught -> { handler.accept(caught); @@ -136,11 +138,12 @@ public EffectHandler catchError(final Class ofType, final Co * instance otherwise */ public EffectHandler catchError(final Consumer handler) { - return error.map(caught -> { - handler.accept(caught); - return EffectHandler.empty(); - }) - .orElse(this); + return this.error + .map(caught -> { + handler.accept(caught); + return EffectHandler.empty(); + }) + .orElse(this); } /** @@ -157,7 +160,8 @@ public EffectHandler runEffect( final ThrowingRunnable onSuccess, final ThrowingConsumer onError ) { - return error.map(Maybe.partialEffect(onError)) + return this.error + .map(Maybe.partialEffect(onError)) .orElseGet(() -> Maybe.fromEffect(onSuccess)); } @@ -182,7 +186,7 @@ public EffectHandler runEffect(final ThrowingRunnable effect) { - error.ifPresent(effect); + this.error.ifPresent(effect); } /** @@ -191,8 +195,8 @@ public void orElse(final Consumer effect) { * @throws E the error thrown by the {@code effect} operation */ public void orThrow() throws E { - if (error.isPresent()) { - throw error.get(); + if (this.error.isPresent()) { + throw this.error.get(); } } @@ -205,8 +209,8 @@ public void orThrow() throws E { * @throws X a mapped exception */ public void orThrow(final Function mapper) throws X { - if (error.isPresent()) { - throw mapper.apply(error.get()); + if (this.error.isPresent()) { + throw mapper.apply(this.error.get()); } } } diff --git a/src/main/java/io/github/joselion/maybe/Maybe.java b/src/main/java/io/github/joselion/maybe/Maybe.java index 8503609..cf675dc 100644 --- a/src/main/java/io/github/joselion/maybe/Maybe.java +++ b/src/main/java/io/github/joselion/maybe/Maybe.java @@ -7,7 +7,7 @@ import org.eclipse.jdt.annotation.Nullable; -import io.github.joselion.maybe.helpers.Common; +import io.github.joselion.maybe.helpers.Commons; import io.github.joselion.maybe.util.function.ThrowingConsumer; import io.github.joselion.maybe.util.function.ThrowingFunction; import io.github.joselion.maybe.util.function.ThrowingRunnable; @@ -37,7 +37,7 @@ private Maybe(final @Nullable T value) { * @return the possible wrapped value */ Optional value() { - return value; + return this.value; } /** @@ -81,7 +81,9 @@ public static Maybe nothing() { * {@link #nothing()} otherwise */ public static Maybe fromOptional(final Optional value) { - return new Maybe<>(value.orElse(null)); + return value + .map(Maybe::new) + .orElseGet(Maybe::nothing); } /** @@ -99,7 +101,7 @@ public static ResolveHandler fromResolver(final T try { return ResolveHandler.ofSuccess(resolver.get()); } catch (Throwable e) { // NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return ResolveHandler.ofError(error); } } @@ -119,7 +121,7 @@ public static EffectHandler fromEffect(final ThrowingRu effect.run(); return EffectHandler.empty(); } catch (Throwable e) { // NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return EffectHandler.ofError(error); } } @@ -233,9 +235,10 @@ public static ResourceHolder so * {@link #nothing()} otherwise */ public Maybe map(final Function mapper) { - return value.map(mapper) - .map(Maybe::just) - .orElseGet(Maybe::nothing); + return Maybe + .fromOptional(this.value) + .resolve(mapper::apply) + .toMaybe(); } /** @@ -252,7 +255,9 @@ public Maybe map(final Function mapper) { * {@link #nothing()} otherwise */ public Maybe flatMap(final Function> mapper) { - return value.map(mapper) + return Maybe + .fromOptional(this.value) + .resolve(mapper::apply) .orElseGet(Maybe::nothing); } @@ -270,11 +275,11 @@ public Maybe flatMap(final Function> mapper) { */ public ResolveHandler resolve(final ThrowingFunction resolver) { try { - return value + return this.value .map(Maybe.partialResolver(resolver)) .orElseThrow(); } catch (final NoSuchElementException e) { - final var error = Common.cast(e); + final var error = Commons.cast(e); return ResolveHandler.ofError(error); } } @@ -290,11 +295,11 @@ public ResolveHandler resolve(final ThrowingFunct */ public EffectHandler runEffect(final ThrowingConsumer effect) { try { - return value + return this.value .map(Maybe.partialEffect(effect)) .orElseThrow(); } catch (final NoSuchElementException e) { - final var error = Common.cast(e); + final var error = Commons.cast(e); return EffectHandler.ofError(error); } } @@ -309,12 +314,10 @@ public EffectHandler runEffect(final ThrowingConsumer Maybe cast(final Class type) { - try { - final var newValue = type.cast(value.orElseThrow()); - return Maybe.just(newValue); - } catch (final ClassCastException error) { - return nothing(); - } + return Maybe + .fromOptional(this.value) + .resolve(type::cast) + .toMaybe(); } /** @@ -323,7 +326,7 @@ public Maybe cast(final Class type) { * @return true if the value is present, false otherwise */ public boolean hasValue() { - return value.isPresent(); + return this.value.isPresent(); } /** @@ -332,7 +335,7 @@ public boolean hasValue() { * @return true if the value is NOT present, false otherwise */ public boolean hasNothing() { - return value.isEmpty(); + return this.value.isEmpty(); } /** @@ -342,7 +345,7 @@ public boolean hasNothing() { * @return an optional with the value, if preset. An empty optional otherwise */ public Optional toOptional() { - return value; + return this.value; } /** @@ -365,7 +368,7 @@ public boolean equals(final Object obj) { if (obj instanceof Maybe) { final var other = (Maybe) obj; - return other.toOptional().equals(value); + return other.toOptional().equals(this.value); } return false; @@ -379,7 +382,7 @@ public boolean equals(final Object obj) { */ @Override public int hashCode() { - return value.hashCode(); + return this.value.hashCode(); } /** @@ -391,9 +394,9 @@ public int hashCode() { */ @Override public String toString() { - return value + return this.value .map(Object::toString) - .map(it -> String.format("Maybe[%s]", it)) + .map(x -> String.format("Maybe[%s]", x)) .orElse("Maybe.nothing"); } } diff --git a/src/main/java/io/github/joselion/maybe/ResolveHandler.java b/src/main/java/io/github/joselion/maybe/ResolveHandler.java index 1bcdc42..ceebbe5 100644 --- a/src/main/java/io/github/joselion/maybe/ResolveHandler.java +++ b/src/main/java/io/github/joselion/maybe/ResolveHandler.java @@ -7,7 +7,7 @@ import org.eclipse.jdt.annotation.Nullable; -import io.github.joselion.maybe.helpers.Common; +import io.github.joselion.maybe.helpers.Commons; import io.github.joselion.maybe.util.Either; import io.github.joselion.maybe.util.function.ThrowingConsumer; import io.github.joselion.maybe.util.function.ThrowingFunction; @@ -97,7 +97,8 @@ public ResolveHandler doOnSuccess(final Consumer effect) { * @return the same handler to continue chainning operations */ public ResolveHandler doOnError(final Class ofType, final Consumer effect) { - this.value.leftToOptional() + this.value + .leftToOptional() .filter(ofType::isInstance) .map(ofType::cast) .ifPresent(effect); @@ -195,7 +196,7 @@ public ResolveHandler resolve( */ public ResolveHandler resolve(final ThrowingFunction resolver) { return this.value - .mapLeft(Common::cast) + .mapLeft(Commons::cast) .unwrap( ResolveHandler::ofError, Maybe.partialResolver(resolver) @@ -234,7 +235,7 @@ public EffectHandler runEffect( */ public EffectHandler runEffect(final ThrowingConsumer effect) { return this.value - .mapLeft(Common::cast) + .mapLeft(Commons::cast) .unwrap( EffectHandler::ofError, Maybe.partialEffect(effect) @@ -446,7 +447,7 @@ public ResourceHolder solve final ThrowingFunction solver ) { return this.value - .mapLeft(Common::cast) + .mapLeft(Commons::cast) .unwrap( ResourceHolder::failure, prev -> diff --git a/src/main/java/io/github/joselion/maybe/ResourceHolder.java b/src/main/java/io/github/joselion/maybe/ResourceHolder.java index ccb5b16..fd4d756 100644 --- a/src/main/java/io/github/joselion/maybe/ResourceHolder.java +++ b/src/main/java/io/github/joselion/maybe/ResourceHolder.java @@ -2,7 +2,7 @@ import java.util.Optional; -import io.github.joselion.maybe.helpers.Common; +import io.github.joselion.maybe.helpers.Commons; import io.github.joselion.maybe.util.Either; import io.github.joselion.maybe.util.function.ThrowingConsumer; import io.github.joselion.maybe.util.function.ThrowingFunction; @@ -61,7 +61,7 @@ static ResourceHolder failu * @return the possible stored resource */ Optional resource() { - return value.rightToOptional(); + return this.value.rightToOptional(); } /** @@ -70,7 +70,7 @@ Optional resource() { * @return the possible propagated error */ Optional error() { - return value.leftToOptional(); + return this.value.leftToOptional(); } /** @@ -91,15 +91,15 @@ Optional error() { * exception to be handled */ public ResolveHandler resolveClosing(final ThrowingFunction resolver) { - return value - .mapLeft(Common::cast) + return this.value + .mapLeft(Commons::cast) .unwrap( ResolveHandler::ofError, resource -> { try (var res = resource) { return ResolveHandler.ofSuccess(resolver.apply(res)); } catch (final Throwable e) { //NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return ResolveHandler.ofError(error); } } @@ -123,8 +123,8 @@ public ResolveHandler resolveClosing(final Throwi * handled or nothing */ public EffectHandler runEffectClosing(final ThrowingConsumer effect) { - return value - .mapLeft(Common::cast) + return this.value + .mapLeft(Commons::cast) .unwrap( EffectHandler::ofError, resource -> { @@ -132,7 +132,7 @@ public EffectHandler runEffectClosing(final ThrowingCon effect.accept(res); return EffectHandler.empty(); } catch (final Throwable e) { // NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return EffectHandler.ofError(error); } } diff --git a/src/main/java/io/github/joselion/maybe/helpers/Common.java b/src/main/java/io/github/joselion/maybe/helpers/Commons.java similarity index 86% rename from src/main/java/io/github/joselion/maybe/helpers/Common.java rename to src/main/java/io/github/joselion/maybe/helpers/Commons.java index 4ebc6be..4471d4d 100644 --- a/src/main/java/io/github/joselion/maybe/helpers/Common.java +++ b/src/main/java/io/github/joselion/maybe/helpers/Commons.java @@ -1,8 +1,8 @@ package io.github.joselion.maybe.helpers; -public class Common { +public class Commons { - Common() { + Commons() { throw new UnsupportedOperationException("Cannot instantiate a helper class"); } diff --git a/src/main/java17/io/github/joselion/maybe/Maybe.java b/src/main/java17/io/github/joselion/maybe/Maybe.java index 78304fc..8004d27 100644 --- a/src/main/java17/io/github/joselion/maybe/Maybe.java +++ b/src/main/java17/io/github/joselion/maybe/Maybe.java @@ -7,7 +7,7 @@ import org.eclipse.jdt.annotation.Nullable; -import io.github.joselion.maybe.helpers.Common; +import io.github.joselion.maybe.helpers.Commons; import io.github.joselion.maybe.util.function.ThrowingConsumer; import io.github.joselion.maybe.util.function.ThrowingFunction; import io.github.joselion.maybe.util.function.ThrowingRunnable; @@ -37,7 +37,7 @@ private Maybe(final @Nullable T value) { * @return the possible wrapped value */ Optional value() { - return value; + return this.value; } /** @@ -81,7 +81,9 @@ public static Maybe nothing() { * {@link #nothing()} otherwise */ public static Maybe fromOptional(final Optional value) { - return new Maybe<>(value.orElse(null)); + return value + .map(Maybe::new) + .orElseGet(Maybe::nothing); } /** @@ -99,7 +101,7 @@ public static ResolveHandler fromResolver(final T try { return ResolveHandler.ofSuccess(resolver.get()); } catch (Throwable e) { // NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return ResolveHandler.ofError(error); } } @@ -119,7 +121,7 @@ public static EffectHandler fromEffect(final ThrowingRu effect.run(); return EffectHandler.empty(); } catch (Throwable e) { // NOSONAR - final var error = Common.cast(e); + final var error = Commons.cast(e); return EffectHandler.ofError(error); } } @@ -233,9 +235,10 @@ public static ResourceHolder so * {@link #nothing()} otherwise */ public Maybe map(final Function mapper) { - return value.map(mapper) - .map(Maybe::just) - .orElseGet(Maybe::nothing); + return Maybe + .fromOptional(this.value) + .resolve(mapper::apply) + .toMaybe(); } /** @@ -252,7 +255,9 @@ public Maybe map(final Function mapper) { * {@link #nothing()} otherwise */ public Maybe flatMap(final Function> mapper) { - return value.map(mapper) + return Maybe + .fromOptional(this.value) + .resolve(mapper::apply) .orElseGet(Maybe::nothing); } @@ -270,11 +275,11 @@ public Maybe flatMap(final Function> mapper) { */ public ResolveHandler resolve(final ThrowingFunction resolver) { try { - return value + return this.value .map(Maybe.partialResolver(resolver)) .orElseThrow(); } catch (final NoSuchElementException e) { - final var error = Common.cast(e); + final var error = Commons.cast(e); return ResolveHandler.ofError(error); } } @@ -290,11 +295,11 @@ public ResolveHandler resolve(final ThrowingFunct */ public EffectHandler runEffect(final ThrowingConsumer effect) { try { - return value + return this.value .map(Maybe.partialEffect(effect)) .orElseThrow(); } catch (final NoSuchElementException e) { - final var error = Common.cast(e); + final var error = Commons.cast(e); return EffectHandler.ofError(error); } } @@ -309,12 +314,10 @@ public EffectHandler runEffect(final ThrowingConsumer Maybe cast(final Class type) { - try { - final var newValue = type.cast(value.orElseThrow()); - return Maybe.just(newValue); - } catch (final ClassCastException error) { - return nothing(); - } + return Maybe + .fromOptional(this.value) + .resolve(type::cast) + .toMaybe(); } /** @@ -323,7 +326,7 @@ public Maybe cast(final Class type) { * @return true if the value is present, false otherwise */ public boolean hasValue() { - return value.isPresent(); + return this.value.isPresent(); } /** @@ -332,7 +335,7 @@ public boolean hasValue() { * @return true if the value is NOT present, false otherwise */ public boolean hasNothing() { - return value.isEmpty(); + return this.value.isEmpty(); } /** @@ -342,7 +345,7 @@ public boolean hasNothing() { * @return an optional with the value, if preset. An empty optional otherwise */ public Optional toOptional() { - return value; + return this.value; } /** @@ -364,7 +367,7 @@ public boolean equals(final Object obj) { } if (obj instanceof final Maybe other) { - return other.toOptional().equals(value); + return other.toOptional().equals(this.value); } return false; @@ -378,7 +381,7 @@ public boolean equals(final Object obj) { */ @Override public int hashCode() { - return value.hashCode(); + return this.value.hashCode(); } /** @@ -390,7 +393,7 @@ public int hashCode() { */ @Override public String toString() { - return value + return this.value .map(Object::toString) .map("Maybe[%s]"::formatted) .orElse("Maybe.nothing"); diff --git a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java b/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java similarity index 99% rename from src/test/java/io/github/joselion/maybe/EffectHandlerTest.java rename to src/test/java/io/github/joselion/maybe/EffectHandlerTests.java index dad9c93..47493cc 100644 --- a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java +++ b/src/test/java/io/github/joselion/maybe/EffectHandlerTests.java @@ -20,7 +20,7 @@ import io.github.joselion.testing.Spy; import io.github.joselion.testing.UnitTest; -@UnitTest class EffectHandlerTest { +@UnitTest class EffectHandlerTests { private static final FileSystemException FAIL_EXCEPTION = new FileSystemException("FAIL"); diff --git a/src/test/java/io/github/joselion/maybe/MaybeTest.java b/src/test/java/io/github/joselion/maybe/MaybeTests.java similarity index 96% rename from src/test/java/io/github/joselion/maybe/MaybeTest.java rename to src/test/java/io/github/joselion/maybe/MaybeTests.java index 5602181..a620532 100644 --- a/src/test/java/io/github/joselion/maybe/MaybeTest.java +++ b/src/test/java/io/github/joselion/maybe/MaybeTests.java @@ -1,591 +1,591 @@ -package io.github.joselion.maybe; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.INPUT_STREAM; -import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; -import static org.assertj.core.api.InstanceOfAssertFactories.optional; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.NoSuchElementException; -import java.util.Optional; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import io.github.joselion.maybe.util.Either; -import io.github.joselion.maybe.util.function.ThrowingConsumer; -import io.github.joselion.maybe.util.function.ThrowingFunction; -import io.github.joselion.maybe.util.function.ThrowingRunnable; -import io.github.joselion.maybe.util.function.ThrowingSupplier; -import io.github.joselion.testing.Spy; -import io.github.joselion.testing.UnitTest; - -@UnitTest class MaybeTest { - - private static final String OK = "OK"; - - private static final IOException FAIL_EXCEPTION = new IOException("FAIL"); - - private final ThrowingFunction failFunction = val -> { - throw FAIL_EXCEPTION; - }; - - private final ThrowingSupplier failSupplier = () -> { - throw FAIL_EXCEPTION; - }; - - private final ThrowingConsumer failConsumer = it -> { - throw FAIL_EXCEPTION; - }; - - private final ThrowingRunnable failRunnable = () -> { - throw FAIL_EXCEPTION; - }; - - @Nested class just { - @Nested class when_a_value_is_passed { - @Test void returns_a_Maybe_wrapping_the_value() { - final var maybe = Maybe.just(OK); - - assertThat(maybe.value()).contains(OK); - } - } - - @Nested class when_null_is_passed { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.just(null); - - assertThat(maybe.value()).isEmpty(); - } - } - } - - @Nested class nothing { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.nothing(); - - assertThat(maybe.value()).isEmpty(); - } - } - - @Nested class fromOptional { - @Nested class when_the_optional_has_a_value { - @Test void returns_a_Maybe_wrapping_the_value() { - final var maybe = Maybe.fromOptional(Optional.of(OK)); - - assertThat(maybe.value()).contains(OK); - } - } - - @Nested class when_the_optional_is_empty { - @Test void returns_a_Maybe_wrapping_nothing() { - final var maybe = Maybe.fromOptional(Optional.empty()); - - assertThat(maybe.value()).isEmpty(); - } - } - } - - @Nested class fromResolver { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_the_value() throws IOException { - final var supplierSpy = Spy.>lambda(() -> OK); - final var handler = Maybe.fromResolver(supplierSpy); - - assertThat(handler.success()).contains(OK); - assertThat(handler.error()).isEmpty(); - - verify(supplierSpy, times(1)).get(); - } - } - - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var supplierSpy = Spy.lambda(failSupplier); - final var handler = Maybe.fromResolver(supplierSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); - - verify(supplierSpy, times(1)).get(); - } - } - } - - @Nested class fromEffect { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_nothing() { - final var runnableSpy = Spy.>lambda(() -> { }); - final var handler = Maybe.fromEffect(runnableSpy); - - assertThat(handler.error()).isEmpty(); - - verify(runnableSpy, times(1)).run(); - } - } - - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var runnableSpy = Spy.lambda(failRunnable); - final var handler = Maybe.fromEffect(runnableSpy); - - assertThat(handler.error()).contains(FAIL_EXCEPTION); - - verify(runnableSpy, times(1)).run(); - } - } - } - - @Nested class partialResolver { - @Test void returns_a_function_that_takes_a_value_and_returns_a_resolve_handler() throws IOException { - final var successSpy = Spy.>lambda(String::length); - final var failureSpy = Spy.lambda(failFunction); - - assertThat(Maybe.partialResolver(successSpy).apply(OK)) - .isInstanceOf(ResolveHandler.class) - .extracting(ResolveHandler::success, optional(Integer.class)) - .contains(OK.length()); - assertThat(Maybe.partialResolver(failureSpy).apply(OK)) - .isInstanceOf(ResolveHandler.class) - .extracting(ResolveHandler::error, optional(IOException.class)) - .contains(FAIL_EXCEPTION); - - verify(successSpy, times(1)).apply(OK); - verify(failureSpy, times(1)).apply(OK); - } - } - - @Nested class partialEffect { - @Test void returns_a_function_that_takes_a_value_and_returns_an_effect_handler() throws IOException { - final var successSpy = Spy.>lambda(v -> { }); - final var failureSpy = Spy.lambda(failConsumer); - - assertThat(Maybe.partialEffect(successSpy).apply(OK)) - .isInstanceOf(EffectHandler.class) - .extracting(EffectHandler::error, optional(RuntimeException.class)) - .isEmpty(); - - assertThat(Maybe.partialEffect(failureSpy).apply(OK)) - .isInstanceOf(EffectHandler.class) - .extracting(EffectHandler::error, optional(IOException.class)) - .contains(FAIL_EXCEPTION); - - verify(successSpy, times(1)).accept(OK); - verify(failureSpy, times(1)).accept(OK); - } - } - - @Nested class withResource { - @Test void returns_the_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { - try (var fis = new FileInputStream("./src/test/resources/readTest.txt")) { - final var holder = Maybe.withResource(fis); - - assertThat(holder.resource()) - .isPresent() - .containsInstanceOf(FileInputStream.class) - .containsSame(fis); - assertThat(holder.error()).isEmpty(); - } - } - } - - @Nested class solveResource { - @Nested class and_the_solver_does_not_throw { - @Test void returns_a_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { - final var path = "./src/test/resources/readTest.txt"; - final var holder = Maybe.solveResource(() -> new FileInputStream(path)); - - assertThat(holder.resource()) - .isPresent() - .containsInstanceOf(FileInputStream.class) - .get() - .asInstanceOf(INPUT_STREAM) - .hasContent("foo"); - assertThat(holder.error()).isEmpty(); - } - } - - @Nested class and_the_solver_throws { - @Test void returns_a_ResourceHolder_with_the_thrown_exception() { - final var holder = Maybe.solveResource(() -> new FileInputStream("invalid.txt")); - - assertThat(holder.resource()).isEmpty(); - assertThat(holder.error()) - .isPresent() - .containsInstanceOf(FileNotFoundException.class); - } - } - } - - @Nested class map { - @Nested class when_the_value_is_present { - @Test void maps_the_value_with_the_passed_function() { - final var maybe = Maybe.just(OK).map(String::length); - - assertThat(maybe.value()).contains(2); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_nothing() { - final var maybe = Maybe.nothing().map(String::length); - - assertThat(maybe.value()).isEmpty(); - } - } - } - - @Nested class flatMap { - @Nested class when_the_value_is_present { - @Test void maps_the_value_with_the_passed_maybe_function() { - final var maybe = Maybe.just(OK) - .flatMap(str -> Maybe.just(str.length())); - - assertThat(maybe.value()).contains(2); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_nothing() { - final var maybe = Maybe.nothing() - .flatMap(str -> Maybe.just(str.length())); - - assertThat(maybe.value()).isEmpty(); - } - } - } - - @Nested class resolve { - @Nested class when_the_value_is_present { - @Test void the_callback_is_called_with_the_value() { - final var functionSpy = Spy.>lambda(v -> OK); - final var handler = Maybe.just(1).resolve(functionSpy); - - assertThat(handler.success()).contains(OK); - assertThat(handler.error()).isEmpty(); - - verify(functionSpy, times(1)).apply(1); - } - } - - @Nested class when_the_value_is_not_present { - @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() throws IOException { - final var functionSpy = Spy.lambda(failFunction); - final var handler = Maybe.nothing().resolve(functionSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(NoSuchElementException.class) - .hasMessage("No value present"); - - verify(functionSpy, never()).apply(any()); - } - } - - @Nested class when_the_new_operation_succeeds { - @Test void returns_a_handler_with_the_resolved_value() { - final var functionSpy = Spy.lambda(ThrowingFunction.identity()); - final var handler = Maybe.just(OK) - .resolve(functionSpy); - - assertThat(handler.success()).contains(OK); - assertThat(handler.error()).isEmpty(); - - verify(functionSpy, times(1)).apply(OK); - } - } - - @Nested class when_the_new_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var functionSpy = Spy.lambda(failFunction); - final var handler = Maybe.just(OK) - .resolve(functionSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); - - verify(functionSpy, times(1)).apply(OK); - } - } - } - - @Nested class runEffect { - @Nested class when_the_value_is_present { - @Test void the_callback_is_called_with_the_value() { - final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); - - assertThat(handler.error()).isEmpty(); - - verify(consumerSpy, times(1)).accept(OK); - } - } - - @Nested class when_the_value_is_not_present { - @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() { - final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.nothing() - .runEffect(consumerSpy); - - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(NoSuchElementException.class) - .hasMessage("No value present"); - - verify(consumerSpy, never()).accept(any()); - } - } - - @Nested class when_the_new_operation_succeeds { - @Test void returns_the_a_handler_with_nothing() { - final var consumerSpy = Spy.>lambda(v -> { }); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); - - assertThat(handler.error()).isEmpty(); - - verify(consumerSpy, times(1)).accept(OK); - } - } - - @Nested class when_the_new_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var consumerSpy = Spy.lambda(failConsumer); - final var handler = Maybe.just(OK) - .runEffect(consumerSpy); - - assertThat(handler.error()).contains(FAIL_EXCEPTION); - - verify(consumerSpy, times(1)).accept(OK); - } - } - } - - @Nested class cast { - @Nested class when_the_value_is_castable_to_the_passed_type { - @Test void returns_a_maybe_with_the_value_cast() { - final var maybe = Maybe.just(3); - - assertThat(maybe.cast(Integer.class).value()).contains(3); - } - } - - @Nested class when_the_value_is_not_castable_to_the_passed_type { - @Test void returns_nothing() { - final var maybe = Maybe.just("3"); - - assertThat(maybe.cast(Integer.class).value()).isEmpty(); - } - } - } - - @Nested class hasValue { - @Nested class when_the_value_is_present { - @Test void returns_true() { - assertThat(Maybe.just(OK).hasValue()).isTrue(); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_false() { - assertThat(Maybe.nothing().hasValue()).isFalse(); - } - } - } - - @Nested class hasNothing { - @Nested class when_the_value_is_not_present { - @Test void returns_true() { - assertThat(Maybe.nothing().hasNothing()).isTrue(); - } - } - - @Nested class when_the_value_is_present { - @Test void returns_false() { - assertThat(Maybe.just(OK).hasNothing()).isFalse(); - } - } - } - - @Nested class toOptional { - @Nested class when_the_value_is_present { - @Test void returns_an_Optional_wrapping_the_value() { - assertThat(Maybe.just(OK).toOptional()) - .contains(OK); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_an_empty_Optional() { - assertThat(Maybe.nothing().toOptional()) - .isEmpty(); - } - } - } - - @Nested class equals { - @Nested class when_the_tested_object_is_the_same_as_the_value { - @Test void returns_true() { - final var maybe = Maybe.just(3); - final var other = maybe; - final var isEqual = maybe.equals(other); - - assertThat(isEqual).isTrue(); - } - } - - @Nested class when_the_tested_object_is_not_the_same_as_the_value { - @Test void returns_false() { - final var maybe = Maybe.just(3); - final var other = (Object) Integer.valueOf(3); - final var isEqual = maybe.equals(other); - - assertThat(isEqual).isFalse(); - } - } - - @Nested class when_both_wrapped_values_are_equal { - @Test void returns_true() { - final var maybe = Maybe.just(OK); - final var other = Maybe.just(OK); - final var isEqual = maybe.equals(other); - - assertThat(isEqual).isTrue(); - } - } - - @Nested class when_both_wrapped_values_are_not_equal { - @Test void returns_false() { - final var maybe = Maybe.just(OK); - final var other = Maybe.just("OTHER"); - final var isEqualToOther = maybe.equals(other); - - assertThat(isEqualToOther).isFalse(); - } - } - } - - @Nested class hashCode { - @Nested class when_the_value_is_present { - @Test void returns_the_hash_code_of_the_value() { - final var maybe = Maybe.just(OK); - - assertThat(maybe).hasSameHashCodeAs(OK); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_zero() { - final var maybe = Maybe.nothing(); - - assertThat(maybe.hashCode()).isZero(); - } - } - } - - @Nested class toString { - @Nested class when_the_value_is_present { - @Test void returns_the_string_representation_of_the_value() { - final var maybe = Maybe.just(OK); - - assertThat(maybe).hasToString("Maybe[OK]"); - } - } - - @Nested class when_the_value_is_not_present { - @Test void returns_the_string_representation_of_nothing() { - final var maybe = Maybe.nothing(); - - assertThat(maybe).hasToString("Maybe.nothing"); - } - } - } - - @Nested class leftOrNull { - @Nested class when_the_left_value_is_present { - @Test void returns_the_left_value() { - final var either = Either.ofLeft("foo"); - - assertThat(either.leftOrNull()).isEqualTo("foo"); - } - } - - @Nested class when_the_right_value_is_present { - @Test void returns_null() { - final var either = Either.ofRight("foo"); - - assertThat(either.leftOrNull()).isNull(); - } - } - } - - @Nested class rightOrNull { - @Nested class when_the_left_value_is_present { - @Test void returns_null() { - final var either = Either.ofLeft("foo"); - - assertThat(either.rightOrNull()).isNull(); - } - } - - @Nested class when_the_right_value_is_present { - @Test void returns_the_right_value() { - final var either = Either.ofRight("foo"); - - assertThat(either.rightOrNull()).isEqualTo("foo"); - } - } - } - - @Nested class leftToOptional { - @Nested class when_the_left_value_is_present { - @Test void returs_an_Optional_with_the_left_value() { - final var either = Either.ofLeft("foo"); - - assertThat(either.leftToOptional()) - .isInstanceOf(Optional.class) - .contains("foo"); - } - } - - @Nested class when_the_right_value_is_present { - @Test void returns_an_empty_Optional() { - final var either = Either.ofRight("foo"); - - assertThat(either.leftToOptional()) - .isExactlyInstanceOf(Optional.class) - .isEmpty(); - } - } - } - - @Nested class rightToOptional { - @Nested class when_the_left_value_is_present { - @Test void returns_an_empty_Optional() { - final var either = Either.ofLeft("foo"); - - assertThat(either.rightToOptional()) - .isInstanceOf(Optional.class) - .isEmpty(); - } - } - - @Nested class when_the_right_value_is_present { - @Test void returs_an_Optional_with_the_right_value() { - final var either = Either.ofRight("foo"); - - assertThat(either.rightToOptional()) - .isExactlyInstanceOf(Optional.class) - .contains("foo"); - } - } - } -} +package io.github.joselion.maybe; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.INPUT_STREAM; +import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; +import static org.assertj.core.api.InstanceOfAssertFactories.optional; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import io.github.joselion.maybe.util.Either; +import io.github.joselion.maybe.util.function.ThrowingConsumer; +import io.github.joselion.maybe.util.function.ThrowingFunction; +import io.github.joselion.maybe.util.function.ThrowingRunnable; +import io.github.joselion.maybe.util.function.ThrowingSupplier; +import io.github.joselion.testing.Spy; +import io.github.joselion.testing.UnitTest; + +@UnitTest class MaybeTests { + + private static final String OK = "OK"; + + private static final IOException FAIL_EXCEPTION = new IOException("FAIL"); + + private final ThrowingFunction failFunction = val -> { + throw FAIL_EXCEPTION; + }; + + private final ThrowingSupplier failSupplier = () -> { + throw FAIL_EXCEPTION; + }; + + private final ThrowingConsumer failConsumer = it -> { + throw FAIL_EXCEPTION; + }; + + private final ThrowingRunnable failRunnable = () -> { + throw FAIL_EXCEPTION; + }; + + @Nested class just { + @Nested class when_a_value_is_passed { + @Test void returns_a_Maybe_wrapping_the_value() { + final var maybe = Maybe.just(OK); + + assertThat(maybe.value()).contains(OK); + } + } + + @Nested class when_null_is_passed { + @Test void returns_a_Maybe_wrapping_nothing() { + final var maybe = Maybe.just(null); + + assertThat(maybe.value()).isEmpty(); + } + } + } + + @Nested class nothing { + @Test void returns_a_Maybe_wrapping_nothing() { + final var maybe = Maybe.nothing(); + + assertThat(maybe.value()).isEmpty(); + } + } + + @Nested class fromOptional { + @Nested class when_the_optional_has_a_value { + @Test void returns_a_Maybe_wrapping_the_value() { + final var maybe = Maybe.fromOptional(Optional.of(OK)); + + assertThat(maybe.value()).contains(OK); + } + } + + @Nested class when_the_optional_is_empty { + @Test void returns_a_Maybe_wrapping_nothing() { + final var maybe = Maybe.fromOptional(Optional.empty()); + + assertThat(maybe.value()).isEmpty(); + } + } + } + + @Nested class fromResolver { + @Nested class when_the_operation_succeeds { + @Test void returns_a_handler_with_the_value() throws IOException { + final var supplierSpy = Spy.>lambda(() -> OK); + final var handler = Maybe.fromResolver(supplierSpy); + + assertThat(handler.success()).contains(OK); + assertThat(handler.error()).isEmpty(); + + verify(supplierSpy, times(1)).get(); + } + } + + @Nested class when_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var supplierSpy = Spy.lambda(failSupplier); + final var handler = Maybe.fromResolver(supplierSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(supplierSpy, times(1)).get(); + } + } + } + + @Nested class fromEffect { + @Nested class when_the_operation_succeeds { + @Test void returns_a_handler_with_nothing() { + final var runnableSpy = Spy.>lambda(() -> { }); + final var handler = Maybe.fromEffect(runnableSpy); + + assertThat(handler.error()).isEmpty(); + + verify(runnableSpy, times(1)).run(); + } + } + + @Nested class when_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var runnableSpy = Spy.lambda(failRunnable); + final var handler = Maybe.fromEffect(runnableSpy); + + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(runnableSpy, times(1)).run(); + } + } + } + + @Nested class partialResolver { + @Test void returns_a_function_that_takes_a_value_and_returns_a_resolve_handler() throws IOException { + final var successSpy = Spy.>lambda(String::length); + final var failureSpy = Spy.lambda(failFunction); + + assertThat(Maybe.partialResolver(successSpy).apply(OK)) + .isInstanceOf(ResolveHandler.class) + .extracting(ResolveHandler::success, optional(Integer.class)) + .contains(OK.length()); + assertThat(Maybe.partialResolver(failureSpy).apply(OK)) + .isInstanceOf(ResolveHandler.class) + .extracting(ResolveHandler::error, optional(IOException.class)) + .contains(FAIL_EXCEPTION); + + verify(successSpy, times(1)).apply(OK); + verify(failureSpy, times(1)).apply(OK); + } + } + + @Nested class partialEffect { + @Test void returns_a_function_that_takes_a_value_and_returns_an_effect_handler() throws IOException { + final var successSpy = Spy.>lambda(v -> { }); + final var failureSpy = Spy.lambda(failConsumer); + + assertThat(Maybe.partialEffect(successSpy).apply(OK)) + .isInstanceOf(EffectHandler.class) + .extracting(EffectHandler::error, optional(RuntimeException.class)) + .isEmpty(); + + assertThat(Maybe.partialEffect(failureSpy).apply(OK)) + .isInstanceOf(EffectHandler.class) + .extracting(EffectHandler::error, optional(IOException.class)) + .contains(FAIL_EXCEPTION); + + verify(successSpy, times(1)).accept(OK); + verify(failureSpy, times(1)).accept(OK); + } + } + + @Nested class withResource { + @Test void returns_the_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { + try (var fis = new FileInputStream("./src/test/resources/readTest.txt")) { + final var holder = Maybe.withResource(fis); + + assertThat(holder.resource()) + .isPresent() + .containsInstanceOf(FileInputStream.class) + .containsSame(fis); + assertThat(holder.error()).isEmpty(); + } + } + } + + @Nested class solveResource { + @Nested class and_the_solver_does_not_throw { + @Test void returns_a_ResourceHolder_with_the_resource() throws FileNotFoundException, IOException { + final var path = "./src/test/resources/readTest.txt"; + final var holder = Maybe.solveResource(() -> new FileInputStream(path)); + + assertThat(holder.resource()) + .isPresent() + .containsInstanceOf(FileInputStream.class) + .get() + .asInstanceOf(INPUT_STREAM) + .hasContent("foo"); + assertThat(holder.error()).isEmpty(); + } + } + + @Nested class and_the_solver_throws { + @Test void returns_a_ResourceHolder_with_the_thrown_exception() { + final var holder = Maybe.solveResource(() -> new FileInputStream("invalid.txt")); + + assertThat(holder.resource()).isEmpty(); + assertThat(holder.error()) + .isPresent() + .containsInstanceOf(FileNotFoundException.class); + } + } + } + + @Nested class map { + @Nested class when_the_value_is_present { + @Test void maps_the_value_with_the_passed_function() { + final var maybe = Maybe.just(OK).map(String::length); + + assertThat(maybe.value()).contains(2); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_nothing() { + final var maybe = Maybe.nothing().map(String::length); + + assertThat(maybe.value()).isEmpty(); + } + } + } + + @Nested class flatMap { + @Nested class when_the_value_is_present { + @Test void maps_the_value_with_the_passed_maybe_function() { + final var maybe = Maybe.just(OK) + .flatMap(str -> Maybe.just(str.length())); + + assertThat(maybe.value()).contains(2); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_nothing() { + final var maybe = Maybe.nothing() + .flatMap(str -> Maybe.just(str.length())); + + assertThat(maybe.value()).isEmpty(); + } + } + } + + @Nested class resolve { + @Nested class when_the_value_is_present { + @Test void the_callback_is_called_with_the_value() { + final var functionSpy = Spy.>lambda(v -> OK); + final var handler = Maybe.just(1).resolve(functionSpy); + + assertThat(handler.success()).contains(OK); + assertThat(handler.error()).isEmpty(); + + verify(functionSpy, times(1)).apply(1); + } + } + + @Nested class when_the_value_is_not_present { + @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() throws IOException { + final var functionSpy = Spy.lambda(failFunction); + final var handler = Maybe.nothing().resolve(functionSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + verify(functionSpy, never()).apply(any()); + } + } + + @Nested class when_the_new_operation_succeeds { + @Test void returns_a_handler_with_the_resolved_value() { + final var functionSpy = Spy.lambda(ThrowingFunction.identity()); + final var handler = Maybe.just(OK) + .resolve(functionSpy); + + assertThat(handler.success()).contains(OK); + assertThat(handler.error()).isEmpty(); + + verify(functionSpy, times(1)).apply(OK); + } + } + + @Nested class when_the_new_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var functionSpy = Spy.lambda(failFunction); + final var handler = Maybe.just(OK) + .resolve(functionSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(functionSpy, times(1)).apply(OK); + } + } + } + + @Nested class runEffect { + @Nested class when_the_value_is_present { + @Test void the_callback_is_called_with_the_value() { + final var consumerSpy = Spy.>lambda(v -> { }); + final var handler = Maybe.just(OK) + .runEffect(consumerSpy); + + assertThat(handler.error()).isEmpty(); + + verify(consumerSpy, times(1)).accept(OK); + } + } + + @Nested class when_the_value_is_not_present { + @Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() { + final var consumerSpy = Spy.>lambda(v -> { }); + final var handler = Maybe.nothing() + .runEffect(consumerSpy); + + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + verify(consumerSpy, never()).accept(any()); + } + } + + @Nested class when_the_new_operation_succeeds { + @Test void returns_the_a_handler_with_nothing() { + final var consumerSpy = Spy.>lambda(v -> { }); + final var handler = Maybe.just(OK) + .runEffect(consumerSpy); + + assertThat(handler.error()).isEmpty(); + + verify(consumerSpy, times(1)).accept(OK); + } + } + + @Nested class when_the_new_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var consumerSpy = Spy.lambda(failConsumer); + final var handler = Maybe.just(OK) + .runEffect(consumerSpy); + + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(consumerSpy, times(1)).accept(OK); + } + } + } + + @Nested class cast { + @Nested class when_the_value_is_castable_to_the_passed_type { + @Test void returns_a_maybe_with_the_value_cast() { + final var maybe = Maybe.just(3); + + assertThat(maybe.cast(Integer.class).value()).contains(3); + } + } + + @Nested class when_the_value_is_not_castable_to_the_passed_type { + @Test void returns_nothing() { + final var maybe = Maybe.just("3"); + + assertThat(maybe.cast(Integer.class).value()).isEmpty(); + } + } + } + + @Nested class hasValue { + @Nested class when_the_value_is_present { + @Test void returns_true() { + assertThat(Maybe.just(OK).hasValue()).isTrue(); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_false() { + assertThat(Maybe.nothing().hasValue()).isFalse(); + } + } + } + + @Nested class hasNothing { + @Nested class when_the_value_is_not_present { + @Test void returns_true() { + assertThat(Maybe.nothing().hasNothing()).isTrue(); + } + } + + @Nested class when_the_value_is_present { + @Test void returns_false() { + assertThat(Maybe.just(OK).hasNothing()).isFalse(); + } + } + } + + @Nested class toOptional { + @Nested class when_the_value_is_present { + @Test void returns_an_Optional_wrapping_the_value() { + assertThat(Maybe.just(OK).toOptional()) + .contains(OK); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_an_empty_Optional() { + assertThat(Maybe.nothing().toOptional()) + .isEmpty(); + } + } + } + + @Nested class equals { + @Nested class when_the_tested_object_is_the_same_as_the_value { + @Test void returns_true() { + final var maybe = Maybe.just(3); + final var other = maybe; + final var isEqual = maybe.equals(other); + + assertThat(isEqual).isTrue(); + } + } + + @Nested class when_the_tested_object_is_not_the_same_as_the_value { + @Test void returns_false() { + final var maybe = Maybe.just(3); + final var other = (Object) Integer.valueOf(3); + final var isEqual = maybe.equals(other); + + assertThat(isEqual).isFalse(); + } + } + + @Nested class when_both_wrapped_values_are_equal { + @Test void returns_true() { + final var maybe = Maybe.just(OK); + final var other = Maybe.just(OK); + final var isEqual = maybe.equals(other); + + assertThat(isEqual).isTrue(); + } + } + + @Nested class when_both_wrapped_values_are_not_equal { + @Test void returns_false() { + final var maybe = Maybe.just(OK); + final var other = Maybe.just("OTHER"); + final var isEqualToOther = maybe.equals(other); + + assertThat(isEqualToOther).isFalse(); + } + } + } + + @Nested class hashCode { + @Nested class when_the_value_is_present { + @Test void returns_the_hash_code_of_the_value() { + final var maybe = Maybe.just(OK); + + assertThat(maybe).hasSameHashCodeAs(OK); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_zero() { + final var maybe = Maybe.nothing(); + + assertThat(maybe.hashCode()).isZero(); + } + } + } + + @Nested class toString { + @Nested class when_the_value_is_present { + @Test void returns_the_string_representation_of_the_value() { + final var maybe = Maybe.just(OK); + + assertThat(maybe).hasToString("Maybe[OK]"); + } + } + + @Nested class when_the_value_is_not_present { + @Test void returns_the_string_representation_of_nothing() { + final var maybe = Maybe.nothing(); + + assertThat(maybe).hasToString("Maybe.nothing"); + } + } + } + + @Nested class leftOrNull { + @Nested class when_the_left_value_is_present { + @Test void returns_the_left_value() { + final var either = Either.ofLeft("foo"); + + assertThat(either.leftOrNull()).isEqualTo("foo"); + } + } + + @Nested class when_the_right_value_is_present { + @Test void returns_null() { + final var either = Either.ofRight("foo"); + + assertThat(either.leftOrNull()).isNull(); + } + } + } + + @Nested class rightOrNull { + @Nested class when_the_left_value_is_present { + @Test void returns_null() { + final var either = Either.ofLeft("foo"); + + assertThat(either.rightOrNull()).isNull(); + } + } + + @Nested class when_the_right_value_is_present { + @Test void returns_the_right_value() { + final var either = Either.ofRight("foo"); + + assertThat(either.rightOrNull()).isEqualTo("foo"); + } + } + } + + @Nested class leftToOptional { + @Nested class when_the_left_value_is_present { + @Test void returs_an_Optional_with_the_left_value() { + final var either = Either.ofLeft("foo"); + + assertThat(either.leftToOptional()) + .isInstanceOf(Optional.class) + .contains("foo"); + } + } + + @Nested class when_the_right_value_is_present { + @Test void returns_an_empty_Optional() { + final var either = Either.ofRight("foo"); + + assertThat(either.leftToOptional()) + .isExactlyInstanceOf(Optional.class) + .isEmpty(); + } + } + } + + @Nested class rightToOptional { + @Nested class when_the_left_value_is_present { + @Test void returns_an_empty_Optional() { + final var either = Either.ofLeft("foo"); + + assertThat(either.rightToOptional()) + .isInstanceOf(Optional.class) + .isEmpty(); + } + } + + @Nested class when_the_right_value_is_present { + @Test void returs_an_Optional_with_the_right_value() { + final var either = Either.ofRight("foo"); + + assertThat(either.rightToOptional()) + .isExactlyInstanceOf(Optional.class) + .contains("foo"); + } + } + } +} diff --git a/src/test/java/io/github/joselion/maybe/ResolveHandlerTest.java b/src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java similarity index 97% rename from src/test/java/io/github/joselion/maybe/ResolveHandlerTest.java rename to src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java index 86cc04a..07ce2e5 100644 --- a/src/test/java/io/github/joselion/maybe/ResolveHandlerTest.java +++ b/src/test/java/io/github/joselion/maybe/ResolveHandlerTests.java @@ -1,576 +1,576 @@ -package io.github.joselion.maybe; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.InstanceOfAssertFactories.INPUT_STREAM; -import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.nio.file.AccessDeniedException; -import java.nio.file.FileSystemException; -import java.util.List; -import java.util.function.Function; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import io.github.joselion.maybe.util.function.ThrowingConsumer; -import io.github.joselion.maybe.util.function.ThrowingFunction; -import io.github.joselion.maybe.util.function.ThrowingSupplier; -import io.github.joselion.testing.Spy; -import io.github.joselion.testing.UnitTest; - -@UnitTest class ResolveHandlerTest { - - private static final String OK = "OK"; - - private static final String OTHER = "OTHER"; - - private static final FileSystemException FAIL_EXCEPTION = new FileSystemException("FAIL"); - - private final ThrowingSupplier throwingOp = () -> { - throw FAIL_EXCEPTION; - }; - - private final ThrowingSupplier okOp = () -> OK; - - @Nested class doOnSuccess { - @Nested class when_the_value_is_present { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(v -> { }); - - Maybe.fromResolver(okOp) - .doOnSuccess(consumerSpy); - - verify(consumerSpy, times(1)).accept(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void never_calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(v -> { }); - - Maybe.fromResolver(throwingOp) - .doOnSuccess(consumerSpy); - - verify(consumerSpy, never()).accept(any()); - } - } - } - - @Nested class doOnError { - @Nested class when_the_error_is_present { - @Nested class and_the_error_type_is_provided { - @Nested class and_the_error_is_an_instance_of_the_provided_type { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(error -> { }); - - Maybe.fromResolver(throwingOp) - .doOnError(FileSystemException.class, consumerSpy); - - verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); - } - } - - @Nested class and_the_error_is_not_an_instance_of_the_provided_type { - @Test void never_calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(error -> { }); - - Maybe.fromResolver(throwingOp) - .doOnError(RuntimeException.class, consumerSpy); - - verify(consumerSpy, never()).accept(any()); - } - } - } - - @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(error -> { }); - - Maybe.fromResolver(throwingOp) - .doOnError(consumerSpy); - - verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); - } - } - } - - @Nested class when_the_value_is_present { - @Test void never_calls_the_effect_callback() { - final var cunsumerSpy = Spy.consumer(error -> { }); - - Maybe.fromResolver(okOp) - .doOnError(RuntimeException.class, cunsumerSpy) - .doOnError(cunsumerSpy); - - verify(cunsumerSpy, never()).accept(any()); - } - } - } - - @Nested class catchError { - @Nested class when_the_error_is_present { - @Nested class and_the_error_type_is_provided { - @Nested class and_the_error_is_an_instance_of_the_provided_type { - @Test void calls_the_handler_function() { - final var functionSpy = Spy.function((FileSystemException e) -> OK); - final var handler = Maybe.fromResolver(throwingOp) - .catchError(FileSystemException.class, functionSpy); - - assertThat(handler.success()).contains(OK); - assertThat(handler.error()).isEmpty(); - - verify(functionSpy, times(1)).apply(FAIL_EXCEPTION); - } - } - - @Nested class and_the_error_is_not_an_instance_of_the_provided_type { - @Test void never_calls_the_handler_function() { - final var functionSpy = Spy.function((AccessDeniedException e) -> OK); - final var handler = Maybe.fromResolver(throwingOp) - .catchError(AccessDeniedException.class, functionSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); - - verify(functionSpy, never()).apply(any()); - } - } - } - - @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_handler_function() { - final var handlerSpy = Spy.function((FileSystemException e) -> OK); - final var resolver = Maybe.fromResolver(throwingOp) - .catchError(handlerSpy); - - assertThat(resolver.success()).contains(OK); - assertThat(resolver.error()).isEmpty(); - - verify(handlerSpy, times(1)).apply(FAIL_EXCEPTION); - } - } - } - - @Nested class when_the_value_is_present { - @Test void never_calls_the_handler_function() { - final var functionSpy = Spy.function((RuntimeException e) -> OK); - final var resolvers = List.of( - Maybe.fromResolver(okOp).catchError(RuntimeException.class, functionSpy), - Maybe.fromResolver(okOp).catchError(functionSpy) - ); - - assertThat(resolvers).isNotEmpty().allSatisfy(resolver -> { - assertThat(resolver.success()).contains(OK); - assertThat(resolver.error()).isEmpty(); - }); - - verify(functionSpy, never()).apply(any()); - } - } - } - - @Nested class resolve { - @Nested class when_the_value_is_present { - @Test void calls_the_resolver_callback_and_returns_a_new_handler() { - final var resolverSpy = Spy.>lambda(String::length); - final var successSpy = Spy.>lambda(String::length); - final var errorSpy = Spy.>lambda(e -> -1); - final var handlers = List.of( - Maybe.fromResolver(okOp).resolve(resolverSpy), - Maybe.fromResolver(okOp).resolve(successSpy, errorSpy) - ); - - assertThat(handlers).isNotEmpty().allSatisfy(handler -> { - assertThat(handler.success()).contains(OK.length()); - assertThat(handler.error()).isEmpty(); - }); - - verify(resolverSpy, times(1)).apply(OK); - verify(successSpy, times(1)).apply(OK); - verify(errorSpy, never()).apply(any()); - } - } - - @Nested class when_the_error_is_present { - @Nested class and_the_error_resolver_is_not_provided { - @Test void never_calls_the_resolver_callback_and_returns_a_handler_with_the_error() { - final var successSpy = Spy.>lambda(String::length); - final var handler = Maybe.fromResolver(throwingOp) - .resolve(successSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(FAIL_EXCEPTION.getClass()) - .isEqualTo(FAIL_EXCEPTION); - - verify(successSpy, never()).apply(any()); - } - } - - @Nested class and_the_error_resolver_is_provided { - @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { - final var successSpy = Spy.>lambda(String::length); - final var errorSpy = Spy.>lambda(e -> -1); - final var handler = Maybe.fromResolver(throwingOp) - .resolve(successSpy, errorSpy); - - assertThat(handler.success()).contains(-1); - assertThat(handler.error()).isEmpty(); - - verify(successSpy, never()).apply(any()); - verify(errorSpy, times(1)).apply(FAIL_EXCEPTION); - } - } - } - } - - @Nested class runEffect { - @Nested class when_the_value_is_present { - @Test void calls_the_resolver_callback_and_returns_a_new_handler() throws FileSystemException { - final var effectSpy = Spy.>lambda(v -> throwingOp.get()); - final var successSpy = Spy.>lambda(v -> throwingOp.get()); - final var errorSpy = Spy.>lambda(err -> { }); - final var handler = Maybe.fromResolver(okOp); - final var newHandlers = List.of( - handler.runEffect(effectSpy), - handler.runEffect(successSpy, errorSpy) - ); - - assertThat(newHandlers).isNotEmpty().allSatisfy(newHandler -> { - assertThat(newHandler.error()).contains(FAIL_EXCEPTION); - }); - - verify(effectSpy, times(1)).accept(OK); - verify(successSpy, times(1)).accept(OK); - verify(errorSpy, never()).accept(any()); - } - } - - @Nested class when_the_error_is_present { - @Nested class and_the_error_callback_is_provided { - @Test void calls_only_the_error_callback_and_returns_a_new_handler() throws FileSystemException { - final var successSpy = Spy.>lambda(v -> { }); - final var errorSpy = Spy.>lambda( - err -> throwingOp.get() - ); - final var handler = Maybe.fromResolver(throwingOp); - final var newHandler = handler.runEffect(successSpy, errorSpy); - - assertThat(newHandler.error()).contains(FAIL_EXCEPTION); - - verify(successSpy, never()).accept(any()); - verify(errorSpy, times(1)).accept(FAIL_EXCEPTION); - } - } - - @Nested class and_the_error_callback_is_not_provided { - @Test void never_calls_the_effect_callback_and_returns_a_handler_with_the_error() throws FileSystemException { - final var effectSpy = Spy.>lambda(v -> throwingOp.get()); - final var handler = Maybe.fromResolver(throwingOp); - final var newHandler = handler.runEffect(effectSpy); - - assertThat(newHandler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(FAIL_EXCEPTION.getClass()) - .isEqualTo(FAIL_EXCEPTION); - - verify(effectSpy, never()).accept(any()); - } - } - } - } - - @Nested class map { - @Nested class when_the_value_is_present { - @Test void returns_a_new_handler_applying_the_mapper_function() { - final var handler = ResolveHandler.ofSuccess("Hello world!") - .map(String::length); - - assertThat(handler.success()).contains(12); - - assertThat(handler.error()).isEmpty(); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_new_handler_with_the_previous_error() { - final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) - .map(Object::toString); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); - } - } - } - - @Nested class cast { - @Nested class when_the_value_is_present { - @Nested class and_the_object_can_be_cast { - @Test void returns_a_new_handler_with_the_cast_value() { - final var anyValue = (Object) "Hello"; - final var handler = ResolveHandler.ofSuccess(anyValue) - .cast(String.class); - - assertThat(handler.success()).contains("Hello"); - assertThat(handler.error()).isEmpty(); - } - } - - @Nested class and_the_object_can_not_be_cast { - @Test void returns_a_new_handler_with_the_cast_exception() { - final var handler = ResolveHandler.ofSuccess(3) - .cast(String.class); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(ClassCastException.class) - .hasMessage("Cannot cast java.lang.Integer to java.lang.String"); - } - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_new_handler_with_a_cast_exception() { - final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) - .cast(String.class); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(ClassCastException.class) - .hasMessage(FAIL_EXCEPTION.getMessage()); - } - } - } - - @Nested class orElse { - @Nested class when_the_value_is_present { - @Test void returns_the_value() { - final var handler = Maybe.fromResolver(okOp); - - assertThat(handler.orElse(OTHER)).isEqualTo(OK); - assertThat(handler.orElse(RuntimeException::getMessage)).isEqualTo(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_the_provided_value() { - final var handler = Maybe.fromResolver(throwingOp); - - assertThat(handler.orElse(OTHER)).isEqualTo(OTHER); - assertThat(handler.orElse(FileSystemException::getMessage)).isEqualTo(FAIL_EXCEPTION.getMessage()); - } - } - } - - @Nested class orElseGet { - @Nested class when_the_value_is_present { - @Test void never_evaluates_the_supplier_and_returns_the_value() { - final var supplierSpy = Spy.supplier(() -> OTHER); - final var handler = Maybe.fromResolver(okOp); - - assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OK); - - verify(supplierSpy, never()).get(); - } - } - - @Nested class when_the_error_is_present { - @Test void evaluates_the_supplier_and_returns_the_produced_value() { - final var supplierSpy = Spy.supplier(() -> OTHER); - final var handler = Maybe.fromResolver(throwingOp); - - assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OTHER); - - verify(supplierSpy, times(1)).get(); - } - } - } - - @Nested class orNull { - @Nested class when_the_value_is_present { - @Test void returns_the_value() { - final var handler = Maybe.fromResolver(okOp); - - assertThat(handler.orNull()).isEqualTo(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_null() { - final var handler = Maybe.fromResolver(throwingOp); - - assertThat(handler.orNull()).isNull(); - } - } - } - - @Nested class orThrow { - @Nested class when_the_value_is_present { - @Test void returns_the_value() throws FileSystemException { - final var functionSpy = Spy.function((RuntimeException error) -> FAIL_EXCEPTION); - final var handler = Maybe.fromResolver(okOp); - - assertThat(handler.orThrow()).isEqualTo(OK); - assertThat(handler.orThrow(functionSpy)).isEqualTo(OK); - - verify(functionSpy, never()).apply(any()); - } - } - - @Nested class when_the_error_is_present { - @Test void throws_the_error() { - final var anotherError = new RuntimeException(OTHER); - final var functionSpy = Spy.function((FileSystemException error) -> anotherError); - final var handler = Maybe.fromResolver(throwingOp); - - assertThatThrownBy(handler::orThrow).isEqualTo(FAIL_EXCEPTION); - assertThatThrownBy(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); - - verify(functionSpy, times(1)).apply(FAIL_EXCEPTION); - } - } - } - - @Nested class toMaybe { - @Nested class when_the_value_is_present { - @Test void returns_a_maybe_with_the_value() { - assertThat(Maybe.fromResolver(okOp).toMaybe().value()) - .contains(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_maybe_with_nothing() { - assertThat(Maybe.fromResolver(throwingOp).toMaybe().value()) - .isEmpty(); - } - } - } - - @Nested class toOptional { - @Nested class when_the_value_is_present { - @Test void returns_the_value_wrapped_in_an_optinal() { - assertThat(Maybe.fromResolver(okOp).toOptional()).contains(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_an_empty_optional() { - assertThat(Maybe.fromResolver(throwingOp).toOptional()).isEmpty(); - } - } - } - - @Nested class toEither { - @Nested class when_the_value_is_present { - @Test void returns_an_Either_with_the_value_on_its_right_side() { - final var either = Maybe.fromResolver(okOp).toEither(); - - assertThat(either.isLeft()).isFalse(); - assertThat(either.isRight()).isTrue(); - assertThat(either.leftOrNull()).isNull(); - assertThat(either.rightOrNull()).isEqualTo(OK); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_an_Either_with_the_error_on_its_left_side() { - final var either = Maybe.fromResolver(throwingOp).toEither(); - - assertThat(either.isLeft()).isTrue(); - assertThat(either.isRight()).isFalse(); - assertThat(either.leftOrNull()).isEqualTo(FAIL_EXCEPTION); - assertThat(either.rightOrNull()).isNull(); - } - } - } - - @Nested class mapToResource { - @Nested class when_the_resource_is_present { - @Test void returns_a_resource_holder_with_the_mapped_value() { - final var path = "./src/test/resources/readTest.txt"; - final var holder = Maybe.just(path) - .resolve(FileInputStream::new) - .mapToResource(Function.identity()); - - assertThat(holder.resource()) - .get(INPUT_STREAM) - .hasContent("foo"); - assertThat(holder.error()).isEmpty(); - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_resource_holder_with_the_propagated_error() { - final var holder = Maybe.just("invalidFile.txt") - .resolve(FileInputStream::new) - .mapToResource(Function.identity()); - - assertThat(holder.resource()).isEmpty(); - assertThat(holder.error()) - .get(THROWABLE) - .isExactlyInstanceOf(FileNotFoundException.class) - .hasMessageStartingWith("invalidFile.txt"); - } - } - } - - @Nested class solveResource { - @Nested class when_the_value_is_present { - @Nested class and_the_solver_does_not_throw { - @Test void returns_a_ResourceHolder_with_the_resource() { - final var path = "./src/test/resources/readTest.txt"; - final var holder = Maybe - .just(path) - .resolve(ThrowingFunction.identity()) - .solveResource(FileInputStream::new); - - assertThat(holder.resource()) - .isPresent() - .containsInstanceOf(FileInputStream.class) - .get() - .asInstanceOf(INPUT_STREAM) - .hasContent("foo"); - assertThat(holder.error()).isEmpty(); - } - } - - @Nested class and_the_solver_throws { - @Test void returns_a_ResourceHolder_with_the_thrown_exception() { - final var holder = Maybe - .just("invalid.txt") - .resolve(ThrowingFunction.identity()) - .solveResource(FileInputStream::new); - - assertThat(holder.resource()).isEmpty(); - assertThat(holder.error()) - .isPresent() - .containsInstanceOf(FileNotFoundException.class); - } - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_ResourceHolder_with_the_propagated_error() { - final var holder = Maybe - .fromResolver(throwingOp) - .solveResource(FileInputStream::new); - - assertThat(holder.resource()).isEmpty(); - assertThat(holder.error()) - .isPresent() - .containsInstanceOf(FileSystemException.class); - } - } - } -} +package io.github.joselion.maybe; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.InstanceOfAssertFactories.INPUT_STREAM; +import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.nio.file.AccessDeniedException; +import java.nio.file.FileSystemException; +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import io.github.joselion.maybe.util.function.ThrowingConsumer; +import io.github.joselion.maybe.util.function.ThrowingFunction; +import io.github.joselion.maybe.util.function.ThrowingSupplier; +import io.github.joselion.testing.Spy; +import io.github.joselion.testing.UnitTest; + +@UnitTest class ResolveHandlerTests { + + private static final String OK = "OK"; + + private static final String OTHER = "OTHER"; + + private static final FileSystemException FAIL_EXCEPTION = new FileSystemException("FAIL"); + + private final ThrowingSupplier throwingOp = () -> { + throw FAIL_EXCEPTION; + }; + + private final ThrowingSupplier okOp = () -> OK; + + @Nested class doOnSuccess { + @Nested class when_the_value_is_present { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(v -> { }); + + Maybe.fromResolver(okOp) + .doOnSuccess(consumerSpy); + + verify(consumerSpy, times(1)).accept(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void never_calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(v -> { }); + + Maybe.fromResolver(throwingOp) + .doOnSuccess(consumerSpy); + + verify(consumerSpy, never()).accept(any()); + } + } + } + + @Nested class doOnError { + @Nested class when_the_error_is_present { + @Nested class and_the_error_type_is_provided { + @Nested class and_the_error_is_an_instance_of_the_provided_type { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); + + Maybe.fromResolver(throwingOp) + .doOnError(FileSystemException.class, consumerSpy); + + verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); + } + } + + @Nested class and_the_error_is_not_an_instance_of_the_provided_type { + @Test void never_calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); + + Maybe.fromResolver(throwingOp) + .doOnError(RuntimeException.class, consumerSpy); + + verify(consumerSpy, never()).accept(any()); + } + } + } + + @Nested class and_the_error_type_is_not_provided { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); + + Maybe.fromResolver(throwingOp) + .doOnError(consumerSpy); + + verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION); + } + } + } + + @Nested class when_the_value_is_present { + @Test void never_calls_the_effect_callback() { + final var cunsumerSpy = Spy.consumer(error -> { }); + + Maybe.fromResolver(okOp) + .doOnError(RuntimeException.class, cunsumerSpy) + .doOnError(cunsumerSpy); + + verify(cunsumerSpy, never()).accept(any()); + } + } + } + + @Nested class catchError { + @Nested class when_the_error_is_present { + @Nested class and_the_error_type_is_provided { + @Nested class and_the_error_is_an_instance_of_the_provided_type { + @Test void calls_the_handler_function() { + final var functionSpy = Spy.function((FileSystemException e) -> OK); + final var handler = Maybe.fromResolver(throwingOp) + .catchError(FileSystemException.class, functionSpy); + + assertThat(handler.success()).contains(OK); + assertThat(handler.error()).isEmpty(); + + verify(functionSpy, times(1)).apply(FAIL_EXCEPTION); + } + } + + @Nested class and_the_error_is_not_an_instance_of_the_provided_type { + @Test void never_calls_the_handler_function() { + final var functionSpy = Spy.function((AccessDeniedException e) -> OK); + final var handler = Maybe.fromResolver(throwingOp) + .catchError(AccessDeniedException.class, functionSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + + verify(functionSpy, never()).apply(any()); + } + } + } + + @Nested class and_the_error_type_is_not_provided { + @Test void calls_the_handler_function() { + final var handlerSpy = Spy.function((FileSystemException e) -> OK); + final var resolver = Maybe.fromResolver(throwingOp) + .catchError(handlerSpy); + + assertThat(resolver.success()).contains(OK); + assertThat(resolver.error()).isEmpty(); + + verify(handlerSpy, times(1)).apply(FAIL_EXCEPTION); + } + } + } + + @Nested class when_the_value_is_present { + @Test void never_calls_the_handler_function() { + final var functionSpy = Spy.function((RuntimeException e) -> OK); + final var resolvers = List.of( + Maybe.fromResolver(okOp).catchError(RuntimeException.class, functionSpy), + Maybe.fromResolver(okOp).catchError(functionSpy) + ); + + assertThat(resolvers).isNotEmpty().allSatisfy(resolver -> { + assertThat(resolver.success()).contains(OK); + assertThat(resolver.error()).isEmpty(); + }); + + verify(functionSpy, never()).apply(any()); + } + } + } + + @Nested class resolve { + @Nested class when_the_value_is_present { + @Test void calls_the_resolver_callback_and_returns_a_new_handler() { + final var resolverSpy = Spy.>lambda(String::length); + final var successSpy = Spy.>lambda(String::length); + final var errorSpy = Spy.>lambda(e -> -1); + final var handlers = List.of( + Maybe.fromResolver(okOp).resolve(resolverSpy), + Maybe.fromResolver(okOp).resolve(successSpy, errorSpy) + ); + + assertThat(handlers).isNotEmpty().allSatisfy(handler -> { + assertThat(handler.success()).contains(OK.length()); + assertThat(handler.error()).isEmpty(); + }); + + verify(resolverSpy, times(1)).apply(OK); + verify(successSpy, times(1)).apply(OK); + verify(errorSpy, never()).apply(any()); + } + } + + @Nested class when_the_error_is_present { + @Nested class and_the_error_resolver_is_not_provided { + @Test void never_calls_the_resolver_callback_and_returns_a_handler_with_the_error() { + final var successSpy = Spy.>lambda(String::length); + final var handler = Maybe.fromResolver(throwingOp) + .resolve(successSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(FAIL_EXCEPTION.getClass()) + .isEqualTo(FAIL_EXCEPTION); + + verify(successSpy, never()).apply(any()); + } + } + + @Nested class and_the_error_resolver_is_provided { + @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { + final var successSpy = Spy.>lambda(String::length); + final var errorSpy = Spy.>lambda(e -> -1); + final var handler = Maybe.fromResolver(throwingOp) + .resolve(successSpy, errorSpy); + + assertThat(handler.success()).contains(-1); + assertThat(handler.error()).isEmpty(); + + verify(successSpy, never()).apply(any()); + verify(errorSpy, times(1)).apply(FAIL_EXCEPTION); + } + } + } + } + + @Nested class runEffect { + @Nested class when_the_value_is_present { + @Test void calls_the_resolver_callback_and_returns_a_new_handler() throws FileSystemException { + final var effectSpy = Spy.>lambda(v -> throwingOp.get()); + final var successSpy = Spy.>lambda(v -> throwingOp.get()); + final var errorSpy = Spy.>lambda(err -> { }); + final var handler = Maybe.fromResolver(okOp); + final var newHandlers = List.of( + handler.runEffect(effectSpy), + handler.runEffect(successSpy, errorSpy) + ); + + assertThat(newHandlers).isNotEmpty().allSatisfy(newHandler -> { + assertThat(newHandler.error()).contains(FAIL_EXCEPTION); + }); + + verify(effectSpy, times(1)).accept(OK); + verify(successSpy, times(1)).accept(OK); + verify(errorSpy, never()).accept(any()); + } + } + + @Nested class when_the_error_is_present { + @Nested class and_the_error_callback_is_provided { + @Test void calls_only_the_error_callback_and_returns_a_new_handler() throws FileSystemException { + final var successSpy = Spy.>lambda(v -> { }); + final var errorSpy = Spy.>lambda( + err -> throwingOp.get() + ); + final var handler = Maybe.fromResolver(throwingOp); + final var newHandler = handler.runEffect(successSpy, errorSpy); + + assertThat(newHandler.error()).contains(FAIL_EXCEPTION); + + verify(successSpy, never()).accept(any()); + verify(errorSpy, times(1)).accept(FAIL_EXCEPTION); + } + } + + @Nested class and_the_error_callback_is_not_provided { + @Test void never_calls_the_effect_callback_and_returns_a_handler_with_the_error() throws FileSystemException { + final var effectSpy = Spy.>lambda(v -> throwingOp.get()); + final var handler = Maybe.fromResolver(throwingOp); + final var newHandler = handler.runEffect(effectSpy); + + assertThat(newHandler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(FAIL_EXCEPTION.getClass()) + .isEqualTo(FAIL_EXCEPTION); + + verify(effectSpy, never()).accept(any()); + } + } + } + } + + @Nested class map { + @Nested class when_the_value_is_present { + @Test void returns_a_new_handler_applying_the_mapper_function() { + final var handler = ResolveHandler.ofSuccess("Hello world!") + .map(String::length); + + assertThat(handler.success()).contains(12); + + assertThat(handler.error()).isEmpty(); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_new_handler_with_the_previous_error() { + final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) + .map(Object::toString); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + } + } + } + + @Nested class cast { + @Nested class when_the_value_is_present { + @Nested class and_the_object_can_be_cast { + @Test void returns_a_new_handler_with_the_cast_value() { + final var anyValue = (Object) "Hello"; + final var handler = ResolveHandler.ofSuccess(anyValue) + .cast(String.class); + + assertThat(handler.success()).contains("Hello"); + assertThat(handler.error()).isEmpty(); + } + } + + @Nested class and_the_object_can_not_be_cast { + @Test void returns_a_new_handler_with_the_cast_exception() { + final var handler = ResolveHandler.ofSuccess(3) + .cast(String.class); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(ClassCastException.class) + .hasMessage("Cannot cast java.lang.Integer to java.lang.String"); + } + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_new_handler_with_a_cast_exception() { + final var handler = ResolveHandler.ofError(FAIL_EXCEPTION) + .cast(String.class); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(ClassCastException.class) + .hasMessage(FAIL_EXCEPTION.getMessage()); + } + } + } + + @Nested class orElse { + @Nested class when_the_value_is_present { + @Test void returns_the_value() { + final var handler = Maybe.fromResolver(okOp); + + assertThat(handler.orElse(OTHER)).isEqualTo(OK); + assertThat(handler.orElse(RuntimeException::getMessage)).isEqualTo(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_the_provided_value() { + final var handler = Maybe.fromResolver(throwingOp); + + assertThat(handler.orElse(OTHER)).isEqualTo(OTHER); + assertThat(handler.orElse(FileSystemException::getMessage)).isEqualTo(FAIL_EXCEPTION.getMessage()); + } + } + } + + @Nested class orElseGet { + @Nested class when_the_value_is_present { + @Test void never_evaluates_the_supplier_and_returns_the_value() { + final var supplierSpy = Spy.supplier(() -> OTHER); + final var handler = Maybe.fromResolver(okOp); + + assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OK); + + verify(supplierSpy, never()).get(); + } + } + + @Nested class when_the_error_is_present { + @Test void evaluates_the_supplier_and_returns_the_produced_value() { + final var supplierSpy = Spy.supplier(() -> OTHER); + final var handler = Maybe.fromResolver(throwingOp); + + assertThat(handler.orElseGet(supplierSpy)).isEqualTo(OTHER); + + verify(supplierSpy, times(1)).get(); + } + } + } + + @Nested class orNull { + @Nested class when_the_value_is_present { + @Test void returns_the_value() { + final var handler = Maybe.fromResolver(okOp); + + assertThat(handler.orNull()).isEqualTo(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_null() { + final var handler = Maybe.fromResolver(throwingOp); + + assertThat(handler.orNull()).isNull(); + } + } + } + + @Nested class orThrow { + @Nested class when_the_value_is_present { + @Test void returns_the_value() throws FileSystemException { + final var functionSpy = Spy.function((RuntimeException error) -> FAIL_EXCEPTION); + final var handler = Maybe.fromResolver(okOp); + + assertThat(handler.orThrow()).isEqualTo(OK); + assertThat(handler.orThrow(functionSpy)).isEqualTo(OK); + + verify(functionSpy, never()).apply(any()); + } + } + + @Nested class when_the_error_is_present { + @Test void throws_the_error() { + final var anotherError = new RuntimeException(OTHER); + final var functionSpy = Spy.function((FileSystemException error) -> anotherError); + final var handler = Maybe.fromResolver(throwingOp); + + assertThatThrownBy(handler::orThrow).isEqualTo(FAIL_EXCEPTION); + assertThatThrownBy(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); + + verify(functionSpy, times(1)).apply(FAIL_EXCEPTION); + } + } + } + + @Nested class toMaybe { + @Nested class when_the_value_is_present { + @Test void returns_a_maybe_with_the_value() { + assertThat(Maybe.fromResolver(okOp).toMaybe().value()) + .contains(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_maybe_with_nothing() { + assertThat(Maybe.fromResolver(throwingOp).toMaybe().value()) + .isEmpty(); + } + } + } + + @Nested class toOptional { + @Nested class when_the_value_is_present { + @Test void returns_the_value_wrapped_in_an_optinal() { + assertThat(Maybe.fromResolver(okOp).toOptional()).contains(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_an_empty_optional() { + assertThat(Maybe.fromResolver(throwingOp).toOptional()).isEmpty(); + } + } + } + + @Nested class toEither { + @Nested class when_the_value_is_present { + @Test void returns_an_Either_with_the_value_on_its_right_side() { + final var either = Maybe.fromResolver(okOp).toEither(); + + assertThat(either.isLeft()).isFalse(); + assertThat(either.isRight()).isTrue(); + assertThat(either.leftOrNull()).isNull(); + assertThat(either.rightOrNull()).isEqualTo(OK); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_an_Either_with_the_error_on_its_left_side() { + final var either = Maybe.fromResolver(throwingOp).toEither(); + + assertThat(either.isLeft()).isTrue(); + assertThat(either.isRight()).isFalse(); + assertThat(either.leftOrNull()).isEqualTo(FAIL_EXCEPTION); + assertThat(either.rightOrNull()).isNull(); + } + } + } + + @Nested class mapToResource { + @Nested class when_the_resource_is_present { + @Test void returns_a_resource_holder_with_the_mapped_value() { + final var path = "./src/test/resources/readTest.txt"; + final var holder = Maybe.just(path) + .resolve(FileInputStream::new) + .mapToResource(Function.identity()); + + assertThat(holder.resource()) + .get(INPUT_STREAM) + .hasContent("foo"); + assertThat(holder.error()).isEmpty(); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_resource_holder_with_the_propagated_error() { + final var holder = Maybe.just("invalidFile.txt") + .resolve(FileInputStream::new) + .mapToResource(Function.identity()); + + assertThat(holder.resource()).isEmpty(); + assertThat(holder.error()) + .get(THROWABLE) + .isExactlyInstanceOf(FileNotFoundException.class) + .hasMessageStartingWith("invalidFile.txt"); + } + } + } + + @Nested class solveResource { + @Nested class when_the_value_is_present { + @Nested class and_the_solver_does_not_throw { + @Test void returns_a_ResourceHolder_with_the_resource() { + final var path = "./src/test/resources/readTest.txt"; + final var holder = Maybe + .just(path) + .resolve(ThrowingFunction.identity()) + .solveResource(FileInputStream::new); + + assertThat(holder.resource()) + .isPresent() + .containsInstanceOf(FileInputStream.class) + .get() + .asInstanceOf(INPUT_STREAM) + .hasContent("foo"); + assertThat(holder.error()).isEmpty(); + } + } + + @Nested class and_the_solver_throws { + @Test void returns_a_ResourceHolder_with_the_thrown_exception() { + final var holder = Maybe + .just("invalid.txt") + .resolve(ThrowingFunction.identity()) + .solveResource(FileInputStream::new); + + assertThat(holder.resource()).isEmpty(); + assertThat(holder.error()) + .isPresent() + .containsInstanceOf(FileNotFoundException.class); + } + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_ResourceHolder_with_the_propagated_error() { + final var holder = Maybe + .fromResolver(throwingOp) + .solveResource(FileInputStream::new); + + assertThat(holder.resource()).isEmpty(); + assertThat(holder.error()) + .isPresent() + .containsInstanceOf(FileSystemException.class); + } + } + } +} diff --git a/src/test/java/io/github/joselion/maybe/ResourceHolderTest.java b/src/test/java/io/github/joselion/maybe/ResourceHolderTests.java similarity index 96% rename from src/test/java/io/github/joselion/maybe/ResourceHolderTest.java rename to src/test/java/io/github/joselion/maybe/ResourceHolderTests.java index 7546054..7b586fd 100644 --- a/src/test/java/io/github/joselion/maybe/ResourceHolderTest.java +++ b/src/test/java/io/github/joselion/maybe/ResourceHolderTests.java @@ -1,151 +1,151 @@ -package io.github.joselion.maybe; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.io.FileInputStream; -import java.io.IOException; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import io.github.joselion.maybe.util.function.ThrowingConsumer; -import io.github.joselion.maybe.util.function.ThrowingFunction; -import io.github.joselion.testing.Spy; -import io.github.joselion.testing.UnitTest; - -@UnitTest class ResourceHolderTest { - - private static final String FILE_PATH = "./src/test/resources/readTest.txt"; - - private static final IOException FAIL_EXCEPTION = new IOException("FAIL"); - - private final ThrowingFunction okResolver = res -> "OK"; - - private final ThrowingFunction errorResolver = res -> { - throw FAIL_EXCEPTION; - }; - - private final ThrowingConsumer noOpEffect = res -> { }; - - private final ThrowingConsumer errorEffect = res -> { - throw FAIL_EXCEPTION; - }; - - @Nested class resolveClosing { - @Nested class when_the_resource_is_present { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_the_value() { - final var fis = getFIS(); - final var resolverSpy = Spy.lambda(okResolver); - final var handler = Maybe.withResource(fis) - .resolveClosing(resolverSpy); - - assertThat(handler.success()).contains("OK"); - assertThat(handler.error()).isEmpty(); - assertThatThrownBy(fis::read) - .isExactlyInstanceOf(IOException.class) - .hasMessage("Stream Closed"); - - verify(resolverSpy).apply(fis); - } - } - - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var fis = getFIS(); - final var resolverSpy = Spy.lambda(errorResolver); - final var handler = Maybe.withResource(fis) - .resolveClosing(resolverSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()).contains(FAIL_EXCEPTION); - assertThatThrownBy(fis::read) - .isExactlyInstanceOf(IOException.class) - .hasMessage("Stream Closed"); - - verify(resolverSpy).apply(fis); - } - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_handler_with_the_propagated_error() throws Throwable { - final var error = new IOException("Something went wrong..."); - final var resolverSpy = Spy.>lambda(fis -> ""); - final var handler = ResourceHolder.failure(error) - .resolveClosing(resolverSpy); - - assertThat(handler.success()).isEmpty(); - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(IOException.class) - .hasMessage(error.getMessage()); - - verify(resolverSpy, never()).apply(any()); - } - } - } - - @Nested class runEffectClosing { - @Nested class when_the_resource_is_present { - @Nested class when_the_operation_succeeds { - @Test void returns_a_handler_with_nothing() { - final var fis = getFIS(); - final var effectSpy = Spy.lambda(noOpEffect); - final var handler = Maybe.withResource(fis) - .runEffectClosing(effectSpy); - - assertThat(handler.error()).isEmpty(); - assertThatThrownBy(fis::read) - .isExactlyInstanceOf(IOException.class) - .hasMessage("Stream Closed"); - - verify(effectSpy).accept(fis); - } - } - - @Nested class when_the_operation_fails { - @Test void returns_a_handler_with_the_error() throws IOException { - final var fis = getFIS(); - final var effectSpy = Spy.lambda(errorEffect); - final var handler = Maybe.withResource(fis) - .runEffectClosing(effectSpy); - - assertThat(handler.error()).contains(FAIL_EXCEPTION); - assertThatThrownBy(fis::read) - .isExactlyInstanceOf(IOException.class) - .hasMessage("Stream Closed"); - - verify(effectSpy).accept(fis); - } - } - } - - @Nested class when_the_error_is_present { - @Test void returns_a_handler_with_the_propagated_error() throws Throwable { - final var error = new IOException("Something went wrong..."); - final var effectSpy = Spy.>lambda(res -> { }); - final var handler = ResourceHolder.failure(error) - .runEffectClosing(effectSpy); - - assertThat(handler.error()) - .get(THROWABLE) - .isExactlyInstanceOf(IOException.class) - .hasMessage(error.getMessage()); - - verify(effectSpy, never()).accept(any()); - } - } - } - - private FileInputStream getFIS() { - return Maybe.just(FILE_PATH) - .resolve(FileInputStream::new) - .orThrow(Error::new); - } -} +package io.github.joselion.maybe; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.io.FileInputStream; +import java.io.IOException; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import io.github.joselion.maybe.util.function.ThrowingConsumer; +import io.github.joselion.maybe.util.function.ThrowingFunction; +import io.github.joselion.testing.Spy; +import io.github.joselion.testing.UnitTest; + +@UnitTest class ResourceHolderTests { + + private static final String FILE_PATH = "./src/test/resources/readTest.txt"; + + private static final IOException FAIL_EXCEPTION = new IOException("FAIL"); + + private final ThrowingFunction okResolver = res -> "OK"; + + private final ThrowingFunction errorResolver = res -> { + throw FAIL_EXCEPTION; + }; + + private final ThrowingConsumer noOpEffect = res -> { }; + + private final ThrowingConsumer errorEffect = res -> { + throw FAIL_EXCEPTION; + }; + + @Nested class resolveClosing { + @Nested class when_the_resource_is_present { + @Nested class when_the_operation_succeeds { + @Test void returns_a_handler_with_the_value() { + final var fis = getFIS(); + final var resolverSpy = Spy.lambda(okResolver); + final var handler = Maybe.withResource(fis) + .resolveClosing(resolverSpy); + + assertThat(handler.success()).contains("OK"); + assertThat(handler.error()).isEmpty(); + assertThatThrownBy(fis::read) + .isExactlyInstanceOf(IOException.class) + .hasMessage("Stream Closed"); + + verify(resolverSpy).apply(fis); + } + } + + @Nested class when_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var fis = getFIS(); + final var resolverSpy = Spy.lambda(errorResolver); + final var handler = Maybe.withResource(fis) + .resolveClosing(resolverSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).contains(FAIL_EXCEPTION); + assertThatThrownBy(fis::read) + .isExactlyInstanceOf(IOException.class) + .hasMessage("Stream Closed"); + + verify(resolverSpy).apply(fis); + } + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_handler_with_the_propagated_error() throws Throwable { + final var error = new IOException("Something went wrong..."); + final var resolverSpy = Spy.>lambda(fis -> ""); + final var handler = ResourceHolder.failure(error) + .resolveClosing(resolverSpy); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(IOException.class) + .hasMessage(error.getMessage()); + + verify(resolverSpy, never()).apply(any()); + } + } + } + + @Nested class runEffectClosing { + @Nested class when_the_resource_is_present { + @Nested class when_the_operation_succeeds { + @Test void returns_a_handler_with_nothing() { + final var fis = getFIS(); + final var effectSpy = Spy.lambda(noOpEffect); + final var handler = Maybe.withResource(fis) + .runEffectClosing(effectSpy); + + assertThat(handler.error()).isEmpty(); + assertThatThrownBy(fis::read) + .isExactlyInstanceOf(IOException.class) + .hasMessage("Stream Closed"); + + verify(effectSpy).accept(fis); + } + } + + @Nested class when_the_operation_fails { + @Test void returns_a_handler_with_the_error() throws IOException { + final var fis = getFIS(); + final var effectSpy = Spy.lambda(errorEffect); + final var handler = Maybe.withResource(fis) + .runEffectClosing(effectSpy); + + assertThat(handler.error()).contains(FAIL_EXCEPTION); + assertThatThrownBy(fis::read) + .isExactlyInstanceOf(IOException.class) + .hasMessage("Stream Closed"); + + verify(effectSpy).accept(fis); + } + } + } + + @Nested class when_the_error_is_present { + @Test void returns_a_handler_with_the_propagated_error() throws Throwable { + final var error = new IOException("Something went wrong..."); + final var effectSpy = Spy.>lambda(res -> { }); + final var handler = ResourceHolder.failure(error) + .runEffectClosing(effectSpy); + + assertThat(handler.error()) + .get(THROWABLE) + .isExactlyInstanceOf(IOException.class) + .hasMessage(error.getMessage()); + + verify(effectSpy, never()).accept(any()); + } + } + } + + private FileInputStream getFIS() { + return Maybe.just(FILE_PATH) + .resolve(FileInputStream::new) + .orThrow(Error::new); + } +} diff --git a/src/test/java/io/github/joselion/maybe/helpers/CommonTest.java b/src/test/java/io/github/joselion/maybe/helpers/CommonTests.java similarity index 80% rename from src/test/java/io/github/joselion/maybe/helpers/CommonTest.java rename to src/test/java/io/github/joselion/maybe/helpers/CommonTests.java index 2363ab8..274920a 100644 --- a/src/test/java/io/github/joselion/maybe/helpers/CommonTest.java +++ b/src/test/java/io/github/joselion/maybe/helpers/CommonTests.java @@ -8,12 +8,12 @@ import io.github.joselion.testing.UnitTest; -@UnitTest class CommonTest { +@UnitTest class CommonTests { @Nested class helper { @Nested class when_the_class_is_instantiated { @Test void throws_an_UnsupportedOperationException() { - assertThatCode(Common::new) + assertThatCode(Commons::new) .isInstanceOf(UnsupportedOperationException.class) .hasMessage("Cannot instantiate a helper class"); } @@ -25,13 +25,13 @@ @Test void returns_the_value_as_the_parameter_type() { final Number value = 3; - assertThat(Common.cast(value)).isInstanceOf(Integer.class); + assertThat(Commons.cast(value)).isInstanceOf(Integer.class); } } @Nested class when_the_value_cannot_be_cast { @Test void throws_a_ClassCastException() { - assertThatCode(() -> Common.cast("3").intValue()) // NOSONAR + assertThatCode(() -> Commons.cast("3").intValue()) // NOSONAR .isInstanceOf(ClassCastException.class); } } diff --git a/src/test/java/io/github/joselion/maybe/util/EitherTest.java b/src/test/java/io/github/joselion/maybe/util/EitherTests.java similarity index 99% rename from src/test/java/io/github/joselion/maybe/util/EitherTest.java rename to src/test/java/io/github/joselion/maybe/util/EitherTests.java index 4f6a617..86119b4 100644 --- a/src/test/java/io/github/joselion/maybe/util/EitherTest.java +++ b/src/test/java/io/github/joselion/maybe/util/EitherTests.java @@ -13,7 +13,7 @@ import io.github.joselion.testing.Spy; import io.github.joselion.testing.UnitTest; -@UnitTest class EitherTest { +@UnitTest class EitherTests { @Nested class ofLeft { @Nested class when_the_value_is_not_null { diff --git a/src/test/java/io/github/joselion/testing/Spy.java b/src/test/java/io/github/joselion/testing/Spy.java index 3fde8d8..fc96731 100644 --- a/src/test/java/io/github/joselion/testing/Spy.java +++ b/src/test/java/io/github/joselion/testing/Spy.java @@ -8,13 +8,13 @@ import org.mockito.Mockito; -import io.github.joselion.maybe.helpers.Common; +import io.github.joselion.maybe.helpers.Commons; public class Spy { public static T lambda(final T lambda) { final var interfaces = lambda.getClass().getInterfaces(); - final var toMock = Common.>cast(interfaces[0]); + final var toMock = Commons.>cast(interfaces[0]); return Mockito.mock(toMock, delegatesTo(lambda)); }