Skip to content

Instantly share code, notes, and snippets.

@aalmiray
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aalmiray/0fe61a633038cde3fa33 to your computer and use it in GitHub Desktop.
Save aalmiray/0fe61a633038cde3fa33 to your computer and use it in GitHub Desktop.
Testing Services/Controllers in a Griffon JavaFX application
dependencies {
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
runtime('log4j:log4j:1.2.17') {
exclude group: 'ant', module: 'ant-nodeps'
exclude group: 'ant', module: 'ant-junit'
exclude group: 'ant-contrib', module: 'ant-contrib'
}
runtime 'org.slf4j:slf4j-log4j12:1.7.7'
testCompile 'org.codehaus.groovy:groovy-all:2.3.4'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'pl.pragmatists:JUnitParams:1.0.2'
testCompile 'com.jayway.awaitility:awaitility-java8:1.6.1'
}
package org.example;
import griffon.util.GriffonNameUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import static org.hamcrest.core.IsNot.not;
public class IsBlank extends BaseMatcher<String> {
@Override
public boolean matches(Object o) {
return o != null && GriffonNameUtils.isBlank(String.valueOf(o));
}
@Override
public void describeTo(Description description) {
description.appendText("blank");
}
public static Matcher<String> isBlank() {
return new IsBlank();
}
public static Matcher<String> isNotBlank() {
return not(isBlank());
}
}
package org.example;
public class LoginException extends RuntimeException {
public LoginException(String message) {
super(message);
}
public LoginException(String message, Throwable cause) {
super(message, cause);
}
}
package org.example;
import griffon.core.GriffonApplication;
import griffon.core.artifact.GriffonService;
import griffon.metadata.ArtifactProviderFor;
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import static griffon.util.GriffonNameUtils.requireNonBlank;
@ArtifactProviderFor(GriffonService.class)
public class LoginService extends AbstractGriffonService {
@Inject
public LoginService(@Nonnull GriffonApplication application) {
super(application);
}
public void login(@Nonnull String username, @Nonnull String password) {
try {
requireNonBlank(username, "Argument 'username' must not be blank");
requireNonBlank(password, "Argument 'password' must not be blank");
if (!"sherlock".equals(username) || !"secret".equals(password)) {
throw new LoginException("Invalid credentials");
}
} catch (LoginException le) {
throw le;
} catch (Exception e) {
throw new LoginException("An error occurred during the login operation", e);
}
}
}
package org.example
import griffon.core.test.GriffonUnitRule
import griffon.core.test.TestFor
import griffon.inject.BindTo
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Unroll
@Unroll
@TestFor(LoginService)
class LoginServiceSpec extends Specification {
private LoginService service
@Rule
GriffonUnitRule griffon
void 'Login with correct credentials'() {
when:
service.login('foo', 'foo')
then:
1 * clientApi.login('foo', 'foo')
}
void 'Login with incorrect credentials'() {
given:
clientApi.login(_, _) >> { throw new RuntimeException() }
when:
service.login(username, password)
then:
thrown(LoginException)
where:
username | password
null | 'foo'
'' | 'foo'
' ' | 'foo'
'foo' | null
'foo' | ''
'foo' | ' '
}
@BindTo(ClientApi)
private ClientApi clientApi = Mock()
}
package org.example;
import griffon.core.test.GriffonUnitRule;
import griffon.core.test.TestFor;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junitparams.JUnitParamsRunner.$;
@RunWith(JUnitParamsRunner.class)
@TestFor(LoginService.class)
public class LoginServiceTest {
private LoginService service;
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule();
@Test
public void loginWithCorrectCredentials() {
service.login("sherlock", "secret");
}
@Test(expected = LoginException.class)
@Parameters(method = "invalidCredentials")
public void loginWithInvalidCredentials(String username, String password) {
service.login(username, password);
}
private Object[] invalidCredentials() {
return $(
$(null, "bar"),
$("", "bar"),
$(" ", "bar"),
$("foo", null),
$("foo", ""),
$("foo", " "),
$("foo", "bar")
);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="146.0" prefWidth="316.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"
fx:controller="org.example.SampleController">
<children>
<Label layoutX="19.0" layoutY="19.0" text="Username:"/>
<TextField fx:id="username" layoutX="99.0" layoutY="14.0" prefHeight="26.0" prefWidth="200.0"/>
<Label layoutX="19.0" layoutY="53.0" text="Password:"/>
<PasswordField fx:id="password" layoutX="99.0" layoutY="48.0" prefHeight="26.0" prefWidth="200.0"/>
<Button fx:id="loginActionTarget" layoutX="99.0" layoutY="83.0"
mnemonicParsing="false" prefWidth="200.0" text="Login"/>
<Label fx:id="loginMessage" layoutX="21.0" layoutY="116.0" prefHeight="16.0" prefWidth="280.0"/>
</children>
</AnchorPane>
package org.example;
import griffon.core.GriffonApplication;
import griffon.core.artifact.GriffonController;
import griffon.core.controller.Action;
import griffon.javafx.support.JavaFXAction;
import griffon.metadata.ArtifactProviderFor;
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
@ArtifactProviderFor(GriffonController.class)
public class SampleController extends AbstractGriffonController {
private SampleModel model;
@Inject
private LoginService loginService;
@Inject
public SampleController(@Nonnull GriffonApplication application) {
super(application);
}
public void setModel(SampleModel model) {
this.model = model;
}
public void login() {
JavaFXAction loginAction = toolkitActionFor("login");
enableLoginAction(loginAction, false);
try {
loginService.login(model.getUsername(), model.getPassword());
updateLoginMessage("Login successful!");
} catch (LoginException e) {
updateLoginMessage(e.getMessage());
} finally {
enableLoginAction(loginAction, true);
}
}
private void enableLoginAction(@Nonnull JavaFXAction loginAction, boolean enabled) {
runInsideUIAsync(() -> {
loginAction.setEnabled(enabled);
if (!enabled) {
model.setLoginMessage("");
}
});
}
private void updateLoginMessage(@Nonnull String message) {
runInsideUIAsync(() -> model.setLoginMessage(message));
}
@Nullable
private JavaFXAction toolkitActionFor(@Nonnull String actionName) {
Action action = getActionManager().actionFor(this, actionName);
return action != null ? (JavaFXAction) action.getToolkitAction() : null;
}
}
package org.example
import griffon.core.artifact.ArtifactManager
import griffon.core.test.GriffonUnitRule
import griffon.core.test.TestFor
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Unroll
import javax.inject.Inject
import static com.jayway.awaitility.Awaitility.await
import static griffon.util.GriffonNameUtils.isBlank
import static java.util.concurrent.TimeUnit.SECONDS
@Unroll
@TestFor(SampleController)
class SampleControllerSpec extends Specification {
static {
new javafx.embed.swing.JFXPanel()
}
private SampleController controller
@Inject
private ArtifactManager artifactManager;
@Rule
GriffonUnitRule griffon
void 'Controller performs several login scenarios'() {
given:
controller.model = artifactManager.newInstance(SampleModel)
when:
controller.model.username = username
controller.model.password = password
controller.invokeAction('login')
await().atMost(5, SECONDS).until { !isBlank(controller.model.loginMessage) }
then:
controller.model.loginMessage == loginMessage
where:
username | password || loginMessage
'sherlock' | 'secret' || 'Login successful!'
'sherlock' | 'watson' || 'Invalid credentials'
}
}
package org.example;
import griffon.core.artifact.ArtifactManager;
import griffon.core.test.GriffonUnitRule;
import griffon.core.test.TestFor;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.inject.Inject;
import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static junitparams.JUnitParamsRunner.$;
import static org.example.IsBlank.isNotBlank;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(JUnitParamsRunner.class)
@TestFor(SampleController.class)
public class SampleControllerTest {
static {
// force initialization JavaFX Toolkit
new javafx.embed.swing.JFXPanel();
}
private SampleController controller;
@Inject
private ArtifactManager artifactManager;
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule();
@Test
@Parameters(method = "credentialsAndOutput")
public void loginActionScenarios(String username, String password, String loginMessage) {
// given:
final SampleModel model = artifactManager.newInstance(SampleModel.class);
controller.setModel(model);
// when:
model.setUsername(username);
model.setPassword(password);
controller.invokeAction("login");
await().atMost(5, SECONDS).until(() -> assertThat(model.getLoginMessage(), isNotBlank()));
// then:
assertThat(model.getLoginMessage(), equalTo(loginMessage));
}
private Object[] credentialsAndOutput() {
return $(
$("sherlock", "secret", "Login successful!"),
$("sherlock", "watson", "Invalid credentials")
);
}
}
package org.example;
import griffon.core.GriffonApplication;
import griffon.core.artifact.GriffonModel;
import griffon.metadata.ArtifactProviderFor;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel;
import javax.annotation.Nonnull;
import javax.inject.Inject;
@ArtifactProviderFor(GriffonModel.class)
public class SampleModel extends AbstractGriffonModel {
private StringProperty username;
private StringProperty password;
private StringProperty loginMessage;
@Inject
public SampleModel(@Nonnull GriffonApplication application) {
super(application);
}
@Nonnull
public final StringProperty usernameProperty() {
if (username == null) {
username = new SimpleStringProperty(this, "username", "");
}
return username;
}
public void setUsername(String username) {
usernameProperty().set(username);
}
public String getUsername() {
return username == null ? null : usernameProperty().get();
}
@Nonnull
public final StringProperty passwordProperty() {
if (password == null) {
password = new SimpleStringProperty(this, "password", "");
}
return password;
}
public void setPassword(String password) {
passwordProperty().set(password);
}
public String getPassword() {
return password == null ? null : passwordProperty().get();
}
@Nonnull
public final StringProperty loginMessageProperty() {
if (loginMessage == null) {
loginMessage = new SimpleStringProperty(this, "loginMessage", "");
}
return loginMessage;
}
public void setLoginMessage(String loginMessage) {
loginMessageProperty().set(loginMessage);
}
public String getLoginMessage() {
return loginMessage == null ? null : loginMessageProperty().get();
}
}
package org.example;
import griffon.core.GriffonApplication;
import griffon.core.artifact.GriffonView;
import griffon.metadata.ArtifactProviderFor;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.codehaus.griffon.runtime.javafx.artifact.AbstractJavaFXGriffonView;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Collections;
@ArtifactProviderFor(GriffonView.class)
public class SampleView extends AbstractJavaFXGriffonView {
private SampleController controller;
private SampleModel model;
@FXML
private TextField username;
@FXML
private PasswordField password;
@FXML
private Label loginMessage;
@Inject
public SampleView(@Nonnull GriffonApplication application) {
super(application);
}
public void setController(SampleController controller) {
this.controller = controller;
}
public void setModel(SampleModel model) {
this.model = model;
}
@Override
public void initUI() {
Stage stage = (Stage) getApplication()
.createApplicationContainer(Collections.<String,Object>emptyMap());
stage.setTitle(getApplication().getConfiguration().getAsString("application.title"));
stage.setScene(init());
stage.sizeToScene();
getApplication().getWindowManager().attach("mainWindow", stage);
}
// build the UI
private Scene init() {
Scene scene = new Scene(new Group());
scene.setFill(Color.WHITE);
Node node = loadFromFXML();
model.usernameProperty().bindBidirectional(username.textProperty());
model.passwordProperty().bindBidirectional(password.textProperty());
model.loginMessageProperty().bindBidirectional(loginMessage.textProperty());
((Group) scene.getRoot()).getChildren().addAll(node);
connectActions(node, controller);
return scene;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment