diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtils.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtils.java new file mode 100644 index 0000000000..e0ec3548be --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtils.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.test.util; + +import java.util.Optional; +import java.util.function.Supplier; + +/** + * Utility class to help with Java exceptions, particularly in test code. + * + * @since 3.0 + * @author Neil Mc Erlean + */ +public class ExceptionUtils +{ + /** This represents a situation where a throwable of an unexpected type was thrown. */ + public static class UnexpectedThrowableException extends RuntimeException + { + private final Class expected; + private final Throwable actual; + + public UnexpectedThrowableException(Class expected, Throwable actual) + { + this.expected = expected; + this.actual = actual; + } + + public Class getExpected() { return this.expected; } + public Throwable getActual() { return this.actual; } + + @Override public String toString() + { + return String.join("Expected ", expected.getSimpleName(), " but ", + actual.getClass().getSimpleName(), " was thrown."); + } + } + + /** This represents a situation where an expected throwable was not thrown. */ + public static class MissingThrowableException extends RuntimeException + { + private final Class expected; + + public MissingThrowableException(Class expected) + { + this.expected = expected; + } + + public Class getExpected() { return this.expected; } + @Override public String toString() + { + return String.join("Expected ", expected.getSimpleName(), " but nothing was thrown."); + } + } + + /** + * Utility method to help with expected exceptions in test code. This can be used in place of {@code try/catch} + * blocks within test code and can sometimes make code more readable. + * A single expected exception would usually be let escape from the test method and be handled e.g. by JUnit's + * {@code @Test(expected="Exception.class")} pattern. + * However if you have multiple expected exceptions in a sequence, you need to either add a sequence of + * {@code try/catch} or use this method. + *

+ * Examples: + *

+ * + * @param expected the class of the expected throwable (subtypes will also match). + * @param code a lambda containing the code block which should throw the expected throwable. + * @param the return type of the code block (which should not matter as it should not complete). + * @param the type of the expected throwable (subtypes will also match). + * @return the expected throwable object if it was thrown. + * @throws UnexpectedThrowableException if a non-matching throwable was thrown out of the code block. + * @throws MissingThrowableException if the expected throwable was not thrown out of the code block. + */ + public static T intercept(final Class expected, final Supplier code) + { + // The code block may throw an exception or it may not. + Optional maybeThrownByCode; + + try + { + // evaluate the lambda + code.get(); + + // It didn't throw an exception. + maybeThrownByCode = Optional.empty(); + } + catch (Throwable t) + { + maybeThrownByCode = Optional.of(t); + } + + Throwable thrownByCode = maybeThrownByCode.orElseThrow(() -> new MissingThrowableException(expected)); + + if (expected.isAssignableFrom(thrownByCode.getClass())) + { + return (T)thrownByCode; + } + else + { + throw new UnexpectedThrowableException(expected, thrownByCode); + } + } +} \ No newline at end of file diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtilsUsageExamplesTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtilsUsageExamplesTest.java new file mode 100644 index 0000000000..e69a07431a --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExceptionUtilsUsageExamplesTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.test.util; + +import org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.MissingThrowableException; +import org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.UnexpectedThrowableException; +import org.junit.Test; + +import java.io.IOException; + +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.intercept; +import static org.junit.Assert.*; + +/** + * Unit tests showing usage of {@link ExceptionUtils}. + * + * @since 3.0 + * @author Neil Mc Erlean + */ +public class ExceptionUtilsUsageExamplesTest +{ + private String goodMethod() { return "hello"; } + + private String badMethod1() { throw new RuntimeException("Bad method"); } + + private String badMethod2() { throw new UnsupportedOperationException("Bad method", new RuntimeException("root cause")); } + + @Test public void swallowExpectedExceptions() + { + // Calling a local method. (An expression lambda) + intercept(RuntimeException.class, () -> badMethod1() ); + + // Executing a block of code. (Requires return statement) + intercept(RuntimeException.class, () -> + { + for (int i = 0; i < 10; i++) { + goodMethod(); + } + // Also works for subtypes of expected exception. + badMethod2(); + return null; + }); + } + + @Test public void examineTheExpectedException() + { + UnsupportedOperationException e = intercept(UnsupportedOperationException.class, () -> badMethod2() ); + assertEquals(RuntimeException.class, e.getCause().getClass()); + } + + @Test(expected=MissingThrowableException.class) + public void expectedExceptionNotThrown() + { + intercept(IOException.class, () -> + { + // Do nothing + return null; + }); + } + + @Test(expected=UnexpectedThrowableException.class) + public void unexpectedExceptionThrown() + { + intercept(IOException.class, () -> + { + throw new UnsupportedOperationException(); + }); + } + + private void onlySideEffectsHere() { throw new IllegalStateException(); } + + private void onlySideEffectsHere(String s) { throw new IllegalStateException(); } + + // If you use lambdas that return void, then they cannot be lambda expressions. They must be blocks. + @Test public void usingVoidLambdas() + { + intercept(IllegalStateException.class, () -> { + onlySideEffectsHere(); + return null; + }); + + intercept(IllegalStateException.class, () -> { + onlySideEffectsHere("hello"); + return null; + }); + } +} \ No newline at end of file