Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@BeforeParam/@AfterParam for Parameterized runner #1435

Merged
merged 18 commits into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/main/java/org/junit/runners/Parameterized.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.junit.runners;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -234,13 +235,54 @@ public class Parameterized extends Suite {
Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class;
}

/**
* Annotation for {@code public static void} methods which should be executed before
* evaluating tests with a particular parameter.
*
* @see org.junit.BeforeClass
* @see org.junit.Before
* @since 4.13
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforeParam {
}

/**
* Annotation for {@code public static void} methods which should be executed after
* evaluating tests with a particular parameter.
*
* @see org.junit.AfterClass
* @see org.junit.After
* @since 4.13
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterParam {
}

/**
* Only called reflectively. Do not use programmatically.
*/
public Parameterized(Class<?> klass) throws Throwable {
super(klass, RunnersFactory.createRunnersForClass(klass));
}

@Override
protected void collectInitializationErrors(List<Throwable> errors) {
super.collectInitializationErrors(errors);
validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, errors);
validatePublicStaticVoidMethods(Parameterized.AfterParam.class, errors);
}

private void validatePublicStaticVoidMethods(Class<? extends Annotation> annotation,
List<Throwable> errors) {
final List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation);
for (FrameworkMethod eachTestMethod : methods) {
eachTestMethod.validatePublicVoid(true, errors);
}
}

private static class RunnersFactory {
private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.junit.runner.RunWith;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;

/**
Expand Down Expand Up @@ -134,8 +137,72 @@ protected void validateFields(List<Throwable> errors) {
}

@Override
protected Statement classBlock(RunNotifier notifier) {
return childrenInvoker(notifier);
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
statement = withBeforeParams(statement);
statement = withAfterParams(statement);
return statement;
}

private Statement withBeforeParams(Statement statement) {
final List<FrameworkMethod> befores = getTestClass()
.getAnnotatedMethods(Parameterized.BeforeParam.class);
return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores);
}

private class RunBeforeParams extends Statement {
private final Statement next;
private final List<FrameworkMethod> befores;

RunBeforeParams(Statement next, List<FrameworkMethod> befores) {
this.next = next;
this.befores = befores;
}

@Override
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) {
final int paramCount = before.getMethod().getParameterTypes().length;
before.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
}
next.evaluate();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse org.junit.internal.runners.statements.RunBefores and pass parameters to it?


private Statement withAfterParams(Statement statement) {
final List<FrameworkMethod> afters = getTestClass()
.getAnnotatedMethods(Parameterized.AfterParam.class);
return afters.isEmpty() ? statement : new RunAfterParams(statement, afters);
}

private class RunAfterParams extends Statement {
private final Statement next;
private final List<FrameworkMethod> afters;

RunAfterParams(Statement next, List<FrameworkMethod> afters) {
this.next = next;
this.afters = afters;
}

@Override
public void evaluate() throws Throwable {
final List<Throwable> errors = new ArrayList<Throwable>();
try {
next.evaluate();
} catch (Throwable e) {
errors.add(e);
} finally {
for (FrameworkMethod each : afters) {
try {
final int paramCount = each.getMethod().getParameterTypes().length;
each.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
} catch (Throwable e) {
errors.add(e);
}
}
}
MultipleFailureException.assertEmpty(errors);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse org.junit.internal.runners.statements.RunAfters and pass parameters to it?

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.experimental.results.PrintableResult.testResult;
Expand All @@ -16,6 +16,7 @@

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
Expand All @@ -24,6 +25,7 @@
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runners.MethodSorters;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
Expand Down Expand Up @@ -259,6 +261,92 @@ public void beforeAndAfterClassAreRun() {
assertEquals("before after ", fLog);
}

@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public static class BeforeParamAndAfterParam {
@BeforeClass
public static void before() {
fLog += "beforeClass ";
}

@Parameterized.BeforeParam
public static void beforeParam(String x) {
fLog += "before(" + x + ") ";
}

@Parameterized.AfterParam
public static void afterParam() {
fLog += "afterParam ";
}

@AfterClass
public static void after() {
fLog += "afterClass ";
}

private final String x;

public BeforeParamAndAfterParam(String x) {
this.x = x;
}

@Parameters
public static Collection<String> data() {
return Arrays.asList("A", "B");
}

@Test
public void first() {
fLog += "first(" + x + ") ";
}

@Test
public void second() {
fLog += "second(" + x + ") ";
}
}

@Test
public void beforeParamAndAfterParamAreRun() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to have tests for

  1. Multiple @BeforeParam methods
  2. Multiple @AfterParam methods
  3. @AfterParam methods with parameters
  4. @BeforeParam methods without parameters
  5. Parameterized tests with more than one parameter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kcooney I've added some more tests and validation for number of parameters of the BeforeParam/AfterParam methods.
Do you think it's OK already?

fLog = "";
final Result result = JUnitCore.runClasses(BeforeParamAndAfterParam.class);
assertEquals(0, result.getFailureCount());
assertEquals("beforeClass before(A) first(A) second(A) afterParam "
+ "before(B) first(B) second(B) afterParam afterClass ", fLog);
}

@RunWith(Parameterized.class)
public static class BeforeParamAndAfterParamError {
@Parameterized.BeforeParam
public void beforeParam(String x) {
}

@Parameterized.AfterParam
private static void afterParam() {
}

public BeforeParamAndAfterParamError(String x) {
}

@Parameters
public static Collection<String> data() {
return Arrays.asList("A", "B");
}

@Test
public void test() {
}
}

@Test
public void beforeParamAndAfterParamValidation() {
fLog = "";
final Result result = JUnitCore.runClasses(BeforeParamAndAfterParamError.class);
assertEquals(1, result.getFailureCount());
assertThat(result.getFailures().get(0).getMessage(), containsString("beforeParam() should be static"));
assertThat(result.getFailures().get(0).getMessage(), containsString("afterParam() should be public"));
}

@RunWith(Parameterized.class)
static public class EmptyTest {
@BeforeClass
Expand Down