TestRule.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;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.mockito.MockitoAnnotations;
/**
* Definition of a test rule (modification of a test statement).
* <p>
* TestRule are the only way (outside of the @Parameters concept) to
* provides test fixtures, action before and after tests, etc. Only one
* <code>TestRule</code> chain is allowed for a test class. If a test class
* extends another one, one chain is allowed for each test class and they will
* be chained from the upper class to the lower one.
* <p>
* Conceptually, a test rule, at execution time, will receive a test statement
* as parameter and compute another test statement.
* <h3>Test Rule chain</h3>
* A test rule chain is public final, non static, field, of type
* <code>TestRule</code>. Once you have the outer test rule, it is possible to
* chain them by using the {@link #around(TestRule)} or
* {@link #around(Supplier)} methods. One single TestRule (which can be used as
* a start of the chain, or later), can be builded by :
* <ul>
* <li>Simply using the constructor (or other way) provided by the rule itself
* (for instance see the {@link ch.powerunit.rules.TestContextRule
* TestContextRule} rule.</li>
* <li>Use the <code>before</code> method that will produce a rule to be
* executed before a test ; The <code>after</code> method will do the same, but
* with a code to be executed after a test.</li>
* </ul>
*
* <h3>Example</h3>
* For instance, for this class:
*
* <pre>
* import org.mockito.Mock;
* import org.mockito.Mockito;
* import ch.powerunit.Rule;
* import ch.powerunit.Test;
* import ch.powerunit.TestRule;
* import ch.powerunit.TestSuite;
*
* public class MockitoRuleTest implements TestSuite {
* public interface Mockable {
* void run();
* }
*
* @Mock
* private Mockable mock;
*
* @Rule
* public final TestRule testRule = mockitoRule()
* .around(before(this::prepare));
*
* public void prepare() {
* Mockito.doThrow(new RuntimeException("test")).when(mock).run();
* }
*
* @Test
* public void testException() {
* assertWhen((p) -> mock.run()).throwException(
* isA(RuntimeException.class));
* }
*
* }
* </pre>
*
* In this case, the defined chain, will first apply the rule provided by
* <code>mockitoRule</code>, and then apply the rule included inside the
* <code>around</code>. The <code>before</code> method usage inside the
* <code>around</code> will ensure the method <code>prepare</code> is executed
* before each test.
* <p>
* The sequence of execution, will be :
* <ol>
* <li>Execute the Mockito initialization.</li>
* <li>Execute the method <code>prepare</code>.</li>
* <li>Execute the test method <code>testException</code>.</li>
* </ol>
*
* @author borettim
*
*/
@FunctionalInterface
public interface TestRule {
/**
* The default implementation of test rule.
* <p>
* The goal of this method is to compute another test statement that will be
* the one to be runned.
*
* @param inner
* the inner statement
* @return the new statement, as modified by this rule.
*/
Statement<TestContext<Object>, Throwable> computeStatement(
Statement<TestContext<Object>, Throwable> inner);
/**
* Build a before testrule.
* <p>
* The passed piece of code (which can for instance an inline definition or
* a reference to a method) that must be executed before the test itself.
*
* @param before
* the code to be used before
* @return the rule chain.
*/
static TestRule before(Runnable before) {
return (i) -> (p) -> {
before.run();// NOSONAR - It is OK to run here
i.run(p);
};
}
/**
* Build a before testrule.
* <p>
* The passed piece of code (which can for instance an inline definition or
* a reference to a method) that must be executed before the test itself.
*
* @param before
* the code to be used before
* @return the rule chain.
* @since 0.4.0
*/
static TestRule before(Consumer<TestContext<Object>> before) {
return (i) -> (p) -> {
before.accept(p);
i.run(p);// NOSONAR - It is OK to run here
};
}
/**
* Build a after testrule.
* <p>
* The passed piece of code (which can for instance an inline definition or
* a reference to a method) that must be executed after the test itself.
*
* @param after
* the code to be used after
* @return the rule chain.
*/
static TestRule after(Runnable after) {
return (i) -> (p) -> {
try {
i.run(p);// NOSONAR - It is OK to run here
} finally {
after.run();// NOSONAR - It is OK to run here
}
};
}
/**
* Build a after testrule.
* <p>
* The passed piece of code (which can for instance an inline definition or
* a reference to a method) that must be executed after the test itself.
*
* @param after
* the code to be used after
* @return the rule chain.
* @since 0.4.0
*/
static TestRule after(Consumer<TestContext<Object>> after) {
return (i) -> (p) -> {
try {
i.run(p);// NOSONAR - It is OK to run here
} finally {
after.accept(p);
}
};
}
/**
* Add an inner rule.
*
* @param inner
* the inner rule
* @return the rule chain.
*/
default TestRule around(TestRule inner) {
return around(() -> inner);
}
/**
* Add an inner rule.
* <p>
* This inner rule is created just before usage, thanks to the
* {@link Supplier} object. This can be used for the case when one rule
* depend on the outcome of a previous one.
*
* @param inner
* the supplier of the inner rule
* @return the rule chain.
*/
default TestRule around(Supplier<TestRule> inner) {
return (i) -> computeStatement((p) -> inner.get().computeStatement(i)
.run(p));
}
/**
* Create a rule to support mockito.
*
* @return the mockitor rule
*/
static TestRule mockitoRule() {
return (i) -> Statement.around(i, (p) -> {
MockitoAnnotations.initMocks(p.getTestSuiteObject());
}, (p) -> {
// Do nothing as default
});
}
}