Last active
August 29, 2015 14:04
-
-
Save aalmiray/0fe61a633038cde3fa33 to your computer and use it in GitHub Desktop.
Testing Services/Controllers in a Griffon JavaFX application
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example; | |
public class LoginException extends RuntimeException { | |
public LoginException(String message) { | |
super(message); | |
} | |
public LoginException(String message, Throwable cause) { | |
super(message, cause); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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