TestListenerRule.java
/**
* Powerunit - A JDK1.8 test framework
* Copyright (C) 2014 Mathieu Boretti.
*
* This file is part of Powerunit
*
* Powerunit is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Powerunit 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.powerunit.rules;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import ch.powerunit.Statement;
import ch.powerunit.TestContext;
import ch.powerunit.TestRule;
import ch.powerunit.exception.AssumptionError;
/**
* This can be use to support taking action depending on the issue of the test.
* <p>
* The purpose is here to provide an interface that can be implemented by rule
* implementor to do action, before, after and on some condition.
* <p>
* Rule implementer should implements this interface and then implement the
* required methods (which are do-nothing method by default).
*
* The order of execution is the following :
* <ul>
* <li>Before the test, the method {@link #onStart(TestContext)} is executed.</li>
* <li>Then the test is executed.</li>
* <li>In case of error/failure, one (and only one) of the next methods is
* executed
* <ol>
* <li>{@link #onFailure(TestContext, AssertionError)} in case of test failure.</li>
* <li>{@link #onError(TestContext, Throwable)} in case of test error.</li>
* <li>{@link #onAssumptionSkip(TestContext, AssumptionError)} in case of test
* skip.</li>
* </ol>
* </li>
* <li>In all case, after the test, and after the previous method in case of
* error/failure, the method {@link #onEnd(TestContext)} is executed.</li>
* </ul>
*
* The {@link ExternalResource} rule implements this interface to provide simple
* use case (action before and always after test).
*
* @author borettim
* @see ExternalResource
*/
public interface TestListenerRule extends TestRule {
/**
* Method used at the start of the test.
* <p>
* Default implementation is to do nothing.
*
* @param context
* the test context
*/
default void onStart(TestContext<Object> context) {
// Do nothing as default
}
/**
* Method used when a failure happened.
* <p>
* Default implementation is to do nothing.
*
* @param context
* the test context
* @param af
* the failure
*/
default void onFailure(TestContext<Object> context, AssertionError af) {
// Do nothing as default
}
/**
* Method used when an error happened.
* <p>
* Default implementation is to do nothing.
*
* @param context
* the test context
* @param error
* the error
*/
default void onError(TestContext<Object> context, Throwable error) {
// Do nothing as default
}
/**
* Method used when an assumption error happened.
* <p>
* Default implementation is to do nothing.
*
* @param context
* the test context
* @param error
* the assumption error
*/
default void onAssumptionSkip(TestContext<Object> context,
AssumptionError error) {
// Do nothing as default
}
/**
* Method used at the end of the test.
* <p>
* Default implementation is to do nothing.
*
* @param context
* the test context
*/
default void onEnd(TestContext<Object> context) {
// Do nothing as default
}
@Override
default Statement<TestContext<Object>, Throwable> computeStatement(
Statement<TestContext<Object>, Throwable> inner) {
return (p) -> {
try {
onStart(p);
inner.run(p);
} catch (AssertionError af) {
onFailure(p, af);
throw af;
} catch (InternalError ie) {
onError(p, ie);
throw ie;
} catch (AssumptionError ie) {
onAssumptionSkip(p, ie);
throw ie;
} catch (Throwable t) {
onError(p, t);
throw t;
} finally {
onEnd(p);
}
};
}
/**
* Build a {@link TestListenerRule} based on the various method.
*
* @param onStart
* {@link #onStart(TestContext) the action to be done before the
* test start}. If null, nothing is done.
* @param onEnd
* {@link #onEnd(TestContext) the action to be done after the
* test end}. If null, nothing is done.
* @param onFailure
* {@link #onFailure(TestContext, AssertionError) the action to
* be done in case of failure}. If null, nothing is done.
* @param onError
* {@link #onError(TestContext, Throwable) the action to be done
* in case of error}. If null, nothing is done.
* @param onAssumptionSkip
* {@link #onAssumptionSkip(TestContext, AssumptionError) the
* action to be done in case of assumption skipped}. If null
* nothing is done.
* @return the Test Rule.
* @since 0.4.0
*/
static TestListenerRule of(Consumer<TestContext<Object>> onStart,
Consumer<TestContext<Object>> onEnd,
BiConsumer<TestContext<Object>, AssertionError> onFailure,
BiConsumer<TestContext<Object>, Throwable> onError,
BiConsumer<TestContext<Object>, AssumptionError> onAssumptionSkip) {
return new TestListenerRule() {
@Override
public void onStart(TestContext<Object> context) {
if (onStart != null) {
onStart.accept(context);
}
}
@Override
public void onFailure(TestContext<Object> context, AssertionError af) {
if (onFailure != null) {
onFailure.accept(context, af);
}
}
@Override
public void onError(TestContext<Object> context, Throwable error) {
if (onError != null) {
onError.accept(context, error);
}
}
@Override
public void onAssumptionSkip(TestContext<Object> context,
AssumptionError error) {
if (onAssumptionSkip != null) {
onAssumptionSkip.accept(context, error);
}
}
@Override
public void onEnd(TestContext<Object> context) {
if (onEnd != null) {
onEnd.accept(context);
}
}
};
}
/**
* Build a {@link TestListenerRule} with only an action at start.
*
* @param onStart
* {@link #onStart(TestContext) the action to be done before the
* test start}.
* @return the Test Rule.
* @since 0.4.0
*/
static TestListenerRule onStart(Consumer<TestContext<Object>> onStart) {
return of(Objects.requireNonNull(onStart, "onStart can't be null"),
null, null, null, null);
}
/**
* Build a {@link TestListenerRule} with only an action at end.
*
* @param onEnd
* {@link #onEnd(TestContext) the action to be done after the
* test end}.
* @return the Test Rule.
* @since 0.4.0
*/
static TestListenerRule onEnd(Consumer<TestContext<Object>> onEnd) {
return of(null, Objects.requireNonNull(onEnd, "onEnd can't be null"),
null, null, null);
}
}