Skip to content

Commit

Permalink
#40: First steps toward container events in JUnit 4
Browse files Browse the repository at this point in the history
- ExecutionEventRecordingEngineExecutionListener records all events,
  i.e. calls of a listener methods, and makes them available to tests.
- ExecutionEvent represents such an event.
- ExecutionEventConditions contains a collection of AssertJ conditions
  to write assertions for ExecutionEvents.
- FunctionUtils collects utility methods for working with
  java.util.function.*.

------------------------------------------------------------------------
On behalf of the community, the JUnit Lambda Team thanks
msg systems ag (http://www.msg-systems.com) for supporting
the JUnit crowdfunding campaign!
------------------------------------------------------------------------
  • Loading branch information
marcphilipp committed Jan 4, 2016
1 parent 9d665a0 commit 0f9f29a
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 7 deletions.
@@ -0,0 +1,41 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.commons.util;

import java.util.function.Function;
import java.util.function.Predicate;

/**
* Collection of utilities for working with {@link Function Functions},
* {@link Predicate Predicates}, etc.
*
* @since 5.0
*/
public class FunctionUtils {

private FunctionUtils() {
/* no-op */
}

/**
* Returns a predicate that first applies the specified function and then
* tests the specified predicate against the result of the function.
*
* @param function the function to apply
* @param predicate the predicate to test against the result of the function
*/
public static <T, V> Predicate<T> where(Function<T, V> function, Predicate<? super V> predicate) {
Preconditions.notNull(function, "function must not be null");
Preconditions.notNull(predicate, "predicate must not be null");
return input -> predicate.test(function.apply(input));
}

}
@@ -0,0 +1,45 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.commons.util;

import static java.util.function.Predicate.isEqual;
import static org.junit.gen5.api.Assertions.*;

import java.util.function.Predicate;

import org.junit.gen5.api.Test;

class FunctionUtilsTests {

@Test
void whereWithNullFunction() {
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> {
FunctionUtils.where(null, o -> true);
});
assertEquals("function must not be null", exception.getMessage());
}

@Test
void whereWithNullPredicate() {
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> {
FunctionUtils.where(o -> o, null);
});
assertEquals("predicate must not be null", exception.getMessage());
}

@Test
void whereWithChecksPredicateAgainstResultOfFunction() {
Predicate<String> combinedPredicate = FunctionUtils.where(String::length, isEqual(3));
assertFalse(combinedPredicate.test("fo"));
assertTrue(combinedPredicate.test("foo"));
assertFalse(combinedPredicate.test("fooo"));
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine;

import static java.util.function.Predicate.isEqual;
import static org.junit.gen5.commons.util.FunctionUtils.where;
import static org.junit.gen5.engine.ExecutionEvent.Type.*;

import java.util.Optional;
import java.util.function.Predicate;

import org.junit.gen5.commons.util.ToStringBuilder;

/**
* Represents an event collected by
* {@link ExecutionEventRecordingEngineExecutionListener}.
*
* @see ExecutionEventConditions
*/
public class ExecutionEvent {

public enum Type {
DYNAMIC_TEST_REGISTERED, SKIPPED, STARTED, FINISHED
}

public static ExecutionEvent dynamicTestRegistered(TestDescriptor testDescriptor) {
return new ExecutionEvent(DYNAMIC_TEST_REGISTERED, testDescriptor, null);
}

public static ExecutionEvent executionSkipped(TestDescriptor testDescriptor, String reason) {
return new ExecutionEvent(SKIPPED, testDescriptor, reason);
}

public static ExecutionEvent executionStarted(TestDescriptor testDescriptor) {
return new ExecutionEvent(STARTED, testDescriptor, null);
}

public static ExecutionEvent executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) {
return new ExecutionEvent(FINISHED, testDescriptor, result);
}

public static Predicate<ExecutionEvent> byType(ExecutionEvent.Type type) {
return where(ExecutionEvent::getType, isEqual(type));
}

public static Predicate<ExecutionEvent> byTestDescriptor(Predicate<? super TestDescriptor> predicate) {
return where(ExecutionEvent::getTestDescriptor, predicate);
}

public static <T> Predicate<ExecutionEvent> byPayload(Class<T> payloadClass, Predicate<? super T> predicate) {
return event -> event.getPayload(payloadClass).filter(predicate).isPresent();
}

private final ExecutionEvent.Type type;
private final TestDescriptor testDescriptor;
private final Object payload;

private ExecutionEvent(ExecutionEvent.Type type, TestDescriptor testDescriptor, Object payload) {
this.type = type;
this.testDescriptor = testDescriptor;
this.payload = payload;
}

public ExecutionEvent.Type getType() {
return type;
}

public TestDescriptor getTestDescriptor() {
return testDescriptor;
}

public <T> Optional<T> getPayload(Class<T> payloadClass) {
return Optional.ofNullable(payload).filter(payloadClass::isInstance).map(payloadClass::cast);
}

@Override
public String toString() {
// @formatter:off
return new ToStringBuilder(this)
.append("type", type)
.append("testDescriptor", testDescriptor)
.append("payload", payload)
.toString();
// @formatter:on
}

}
@@ -0,0 +1,60 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine;

import static org.assertj.core.api.Assertions.allOf;
import static org.junit.gen5.engine.ExecutionEvent.*;
import static org.junit.gen5.engine.ExecutionEvent.Type.*;
import static org.junit.gen5.engine.TestExecutionResult.Status.*;
import static org.junit.gen5.engine.TestExecutionResultConditions.status;

import org.assertj.core.api.Condition;
import org.junit.gen5.engine.ExecutionEvent.Type;

/**
* Collection of AssertJ conditions for {@link ExecutionEvent}.
*/
public class ExecutionEventConditions {

public static Condition<ExecutionEvent> test() {
return new Condition<>(byTestDescriptor(TestDescriptor::isTest), "is a test");
}

public static Condition<ExecutionEvent> container() {
return new Condition<>(byTestDescriptor(TestDescriptor::isContainer), "is a container");
}

public static Condition<ExecutionEvent> started() {
return type(STARTED);
}

public static Condition<ExecutionEvent> finishedWithFailure(Condition<TestExecutionResult> causeMessage) {
return finished(allOf(status(FAILED), causeMessage));
}

public static Condition<ExecutionEvent> finishedSuccessfully() {
return finished(status(SUCCESSFUL));
}

public static Condition<ExecutionEvent> finished(Condition<TestExecutionResult> resultCondition) {
return allOf(type(FINISHED), result(resultCondition));
}

public static Condition<ExecutionEvent> type(Type expectedType) {
return new Condition<>(byType(expectedType), "type is %s", expectedType);
}

public static Condition<ExecutionEvent> result(Condition<TestExecutionResult> condition) {
return new Condition<>(byPayload(TestExecutionResult.class, condition::matches), "event with result where %s",
condition);
}

}
@@ -0,0 +1,54 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* {@link EngineExecutionListener} that records all events and makes them
* available to tests.
*
* @see ExecutionEvent
*/
public class ExecutionEventRecordingEngineExecutionListener implements EngineExecutionListener {

public final List<ExecutionEvent> executionEvents = new CopyOnWriteArrayList<>();

@Override
public void dynamicTestRegistered(TestDescriptor testDescriptor) {
addEvent(ExecutionEvent.dynamicTestRegistered(testDescriptor));
}

@Override
public void executionSkipped(TestDescriptor testDescriptor, String reason) {
addEvent(ExecutionEvent.executionSkipped(testDescriptor, reason));
}

@Override
public void executionStarted(TestDescriptor testDescriptor) {
addEvent(ExecutionEvent.executionStarted(testDescriptor));
}

@Override
public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) {
addEvent(ExecutionEvent.executionFinished(testDescriptor, result));
}

public List<ExecutionEvent> getExecutionEvents() {
return executionEvents;
}

private void addEvent(ExecutionEvent dynamicTestRegistered) {
executionEvents.add(dynamicTestRegistered);
}

}
@@ -0,0 +1,35 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine;

import static java.util.function.Predicate.isEqual;
import static org.junit.gen5.commons.util.FunctionUtils.where;

import org.assertj.core.api.Condition;
import org.junit.gen5.engine.TestExecutionResult.Status;

/**
* Collection of AssertJ conditions for {@link TestExecutionResult}.
*/
public class TestExecutionResultConditions {

public static Condition<TestExecutionResult> status(Status expectedStatus) {
return new Condition<>(where(TestExecutionResult::getStatus, isEqual(expectedStatus)), "status is %s",
expectedStatus);
}

public static Condition<TestExecutionResult> causeMessage(String expectedMessage) {
return new Condition<TestExecutionResult>(where(TestExecutionResult::getThrowable, throwable -> {
return throwable.isPresent() && expectedMessage.equals(throwable.get().getMessage());
}), "message of cause is %s", expectedMessage);
}

}
Expand Up @@ -10,25 +10,34 @@

package org.junit.gen5.engine.junit4;

import static org.junit.gen5.api.Assertions.assertEquals;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.data.Index.atIndex;
import static org.junit.gen5.engine.ExecutionEventConditions.*;
import static org.junit.gen5.engine.TestExecutionResultConditions.causeMessage;
import static org.junit.gen5.engine.TestPlanSpecification.*;

import org.junit.gen5.api.Test;
import org.junit.gen5.engine.EngineAwareTestDescriptor;
import org.junit.gen5.engine.ExecutionEventRecordingEngineExecutionListener;
import org.junit.gen5.engine.ExecutionRequest;
import org.junit.gen5.engine.TrackingEngineExecutionListener;
import org.junit.gen5.engine.junit4.samples.PlainJUnit4TestCaseWithSingleTestWhichFails;

class JUnit4TestEngineClassExecutionTests {

TrackingEngineExecutionListener listener = new TrackingEngineExecutionListener();
ExecutionEventRecordingEngineExecutionListener listener = new ExecutionEventRecordingEngineExecutionListener();

@Test
void executesPlainJUnit4TestCaseWithSingleTestWhichFails() {
execute(PlainJUnit4TestCaseWithSingleTestWhichFails.class);

assertEquals(1, listener.testStartedCount.get());
assertEquals(1, listener.testFailedCount.get());
// @formatter:off
assertThat(listener.getExecutionEvents())
.hasSize(4)
.has(allOf(container(), started()), atIndex(0))
.has(allOf(test(), started()), atIndex(1))
.has(allOf(test(), finishedWithFailure(causeMessage("this test should fail"))), atIndex(2))
.has(allOf(container(), finishedSuccessfully()), atIndex(3));
// @formatter:on
}

private void execute(Class<?> testClass) {
Expand Down

0 comments on commit 0f9f29a

Please sign in to comment.