package org.codefx.libfx.concurrent.when;
import static org.junit.Assert.assertEquals;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import org.junit.Before;
import org.junit.Test;
/**
* Tests the class {@link ExecuteAlwaysWhen}.
*/
public class ExecuteAlwaysWhenTest {
// #begin FIELDS & INITIALIZATION
/**
* The string which passes the {@link #ACTION_GATEWAY}.
*/
private static final String ACTION_STRING = "action!";
/**
* A string which does not pass the {@link #ACTION_GATEWAY}.
*/
private static final String NO_ACTION_STRING = "no action...";
/**
* The gateway which has to be passed for the action to be executed.
*/
private static final Predicate<String> ACTION_GATEWAY = string -> Objects.equals(string, ACTION_STRING);
/**
* The observable on which is acted.
*/
private Property<String> observable;
/**
* The action which is undertaken. Increases {@link #executedActionCount}.
*/
private Consumer<String> action;
/**
* Counts how many actions were executed.
*/
private AtomicInteger executedActionCount;
/**
* Initializes the instances used to test.
*/
@Before
public void setUp() {
observable = new SimpleStringProperty(NO_ACTION_STRING);
executedActionCount = new AtomicInteger(0);
action = string -> executedActionCount.incrementAndGet();
}
// #end FIELDS & INITIALIZATION
// #begin SINGLE-THREADED TESTS
/**
* Tests whether an {@link IllegalStateException} is thrown when {@link ExecuteAlwaysWhen#executeWhen()
* executeWhen()} is called for the second time.
*/
@Test(expected = IllegalStateException.class)
public void testThrowExceptionIfCallActTwice() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
execute.executeWhen();
}
/**
* Tests whether no action is executed if the initial value does not pass the {@link #ACTION_GATEWAY}.
*/
@Test
public void testDoNotActIfInitialValueWrong() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
assertEquals(0, executedActionCount.get());
}
/**
* Tests whether the action is executed when the initial value passes the {@link #ACTION_GATEWAY}.
*/
@Test
public void testExecuteWhenWhenInitialValueCorrect() {
observable.setValue(ACTION_STRING);
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
assertEquals(1, executedActionCount.get());
}
/**
* Tests whether the action is executed repeatedly.
*/
@Test
public void testExecuteWhenRepeatedlyWhenInitialValueWasCorrect() {
observable.setValue(ACTION_STRING);
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
// this executes the action for the first time
execute.executeWhen();
// change the value and set the action string again; this must execute the action again
observable.setValue(NO_ACTION_STRING);
observable.setValue(ACTION_STRING);
assertEquals(2, executedActionCount.get());
}
/**
* Tests whether the action is executed when the value is changed to one which passes the {@link #ACTION_GATEWAY}
* after waiting began.
*/
@Test
public void testExecuteWhenWhenCorrectValueIsObserved() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
observable.setValue(ACTION_STRING);
assertEquals(1, executedActionCount.get());
}
/**
* Tests whether the action is executed repeatedly.
*/
@Test
public void testExecuteWhenRepeatedlyWhenCorrectValueWasObserved() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
// this executes the action for the first time
observable.setValue(ACTION_STRING);
// change the value and set the action string again; this must execute the action again
observable.setValue(NO_ACTION_STRING);
observable.setValue(ACTION_STRING);
assertEquals(2, executedActionCount.get());
}
/**
* Tests whether {@link ExecuteAlwaysWhen#cancel()} correctly prevents the execution of the action if it was not yet
* executed.
*/
@Test
public void testCancelAfterNoAction() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
// cancel and then set the value, which would lead to action execution
execute.cancel();
observable.setValue(ACTION_STRING);
assertEquals(0, executedActionCount.get());
}
/**
* Tests whether {@link ExecuteAlwaysWhen#cancel()} correctly prevents the execution of the action if the initial
* value was correct.
*/
@Test
public void testCancelAfterInitialValueWasCorrect() {
observable.setValue(ACTION_STRING);
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
// this executes the action for the first time
execute.executeWhen();
// cancel and then reset the value, which would lead to action execution
execute.cancel();
observable.setValue(NO_ACTION_STRING);
observable.setValue(ACTION_STRING);
assertEquals(1, executedActionCount.get());
}
/**
* Tests whether {@link ExecuteAlwaysWhen#cancel()} correctly prevents the execution of the action if the correct
* value was already observed.
*/
@Test
public void testCancelAfterCorrectValueWasObserved() {
ExecuteAlwaysWhen<String> execute = new ExecuteAlwaysWhen<>(observable, ACTION_GATEWAY, action);
execute.executeWhen();
// this executes the action for the first time
observable.setValue(ACTION_STRING);
// cancel and then reset the value, which would lead to action execution
execute.cancel();
observable.setValue(NO_ACTION_STRING);
observable.setValue(ACTION_STRING);
assertEquals(1, executedActionCount.get());
}
// #end SINGLE-THREADED TESTS
// #begin MULTI-THREADED TESTS
/*
* Unfortunately I could not come up with multi-threaded tests... :( The problem is that the only interesting part
* where threads interact is during the call to 'executeWhen'. So to check whether everything works as intended, it
* would be necessary to precisely count the number of actions executed during that call. Due to threading this
* seems impossible to do precisely; and because the time window in which the measurement would have to take place
* is so tiny (the method doesn't do much after all), I expect the margin of error to be so big to make any result
* meaningless.
*/
// #end MULTI-THREADED TESTS
}