MEILLEURES PRATIQUES
DE DÉVELOPPEMENT
GR CE À GWT, GWTP ET JUKITO
Conference by Christian Goudreau et Christopher Viel
au Département Génie Informatique de l'université de Sherbrooke.
Christopher Viel is Software Engineer at Arcbees.
You can follow Christian on Google+ :+ChristopherVielArcbees
Christian Goudreau is BEE-EO AND CO-FOUNDER
at Arcbees.
You can follow Christian on Twitter : @imchrisgoudreau
Christian Goudreau, ArcBees’ CEO, is a self-made entrepreneur with significant experience in project management. Christian has been managing major software development projects since his early teens, and therefore has quickly learned how to juggle heavy responsibilities and deliver.
A talented guest speaker, recognized expert in software architecture and developer tools, his services are much sought-after, not only in Quebec but also in Europe and the United States, where he takes great pleasure in sharing his technical knowledge and his passion for business.
Christian Goudreau was named Young Business Person of the Year, technology & research division, at the Jeune personnalité d’affaires Banque Nationale competition organized by the Jeune chambre de commerce de Québec (JCCQ), in 2012. He was also awarded the Creativity and Innovation Prize, and the Grand Prize at the 2013 Annual LOJIQ awards (the Quebec International Youth Offices).
3. Partie 1
Présentation d’Arcbees
Partie 2
Résumé du MVP Model-View-Presenter
Partie 3
Survol de GWTP Framework MVP pour GWT
Partie 4
Communication serveur Rest-Dispatch
Slides disponibles à https://goo.gl/PBzmwL
4. Partie 5
Tests unitaires Exemples avec Mockito et Jukito
Partie 6
Meilleures pratiques
Partie 7
GWT 3.0
Slides disponibles à https://goo.gl/PBzmwL
8. Notre
mission
Créer des produits permettant à nos clients,
nos abeilles et la communauté
de développeurs d'optimiser leurs processus,
habitudes et façons de penser,
en créant continuellement de la valeur et en
transformant la façon dont ils travaillent.
Partie 1 Arcbees
9. Origine
de Arcbees
Afin d'améliorer leur efficacité
de production, les deux cofondateurs ont
conçu GWT-Platform, un outil open source
de création d'applications web doté
d'une incroyable capacité d'adaptation
accélérant la vitesse de développement
des applications.
Partie 1 Arcbees
10.
11. Évolution
rapide
Rapidement, GWT-Platform a généré
une importante demande de soutien
de la part de ses utilisateurs
et les deux entrepreneurs n'ont eu d'autres
choix que de créer officiellement Arcbees
le 16 juillet 2010 afin de répondre
à ces requêtes.
Partie 1 Arcbees
12. Reconnaissance
de Google
En moins d'un an, Google a remarqué
la jeune entreprise qui a été invitée à
donner une conférence au Google I/O,
puis, en 2012, Christian a été nommé
sur le Comité de direction de GWT,
groupe en charge de la direction
et de l'évolution du framework.
Partie 1 Arcbees
21. ➢ Model : Définit les données qui seront ultimement
gérées dans l’interface utilisateur.
➢ View : Affiche le modèle pour l’utilisateur.
Généralement passive: seulement de la logique d’
affichage. Délègue les interactions utilisateur
(events) au presenter.
➢ Presenter : Récupère et met à jour le modèle.
Interprète le modèle les données pour
les transférer à la vue.
Partie 2 MVP
M
V
P
22. Caractéristiques
du MVP
➢ Couplage faible entre
les différentes couches.
➢ Flot très simple,
presque linéaire.
➢ Communique exclusivement
au moyen d’interfaces.
➢ Vue passive. Ne prend
aucune décision et notifie le
presenter.
Partie 2 MVP
25. Quand l’utiliser
➢ Idéal lorsqu’un interface utilisateur est
nécessaire pour contrôler l’application.
➢ Pratique lorsqu’il y a beaucoup d’édition
de données (formulaires, CRUD, etc.)
➢ Simple à mettre en place lorsqu’une
communication entre client - serveur
est requise.
Partie 2 MVP
Quand
l’utiliser
26. Quand l’utiliser
➢ Simple à tester : Le faible
couplage permet de bien
isoler les couches.
➢ Réutilisation de code plus aisée :
Le flot plus simple et l’utilisation
intensive d’interfaces permet de
mieux extraire le code
réutilisable.
Partie 2 MVP
Les bénéfices et
les désavantages
27. Quand l’utiliser
➢ Flexible : Il est facile de
remplacer n’importe quel
composant
tant qu’on respecte le contrat
défini par les interfaces.
➢ Peut se combiner à d’autres
design patterns.
Partie 2 MVP
Les bénéfices et
les désavantages
28. ➢ Le grand nombre d’interface à
gérer augmente la complexité du
code. Les IDEs modernes aident
néanmoins à atténuer ce
problème (détection des
implémentations).
➢ La vue passive introduit
beaucoup de code boilerplate:
beaucoup d’aller-retour entre le
presenter et la vue.
Partie 2 MVP
Les bénéfices et
les désavantages
30. ➢ Repose sur GWT et GIN (GWT INjection).
➢ Abstraire la gestion du pattern MVP,
mais aussi imposer son utilisation.
➢ Abstraire la gestion cliente: initialiser l’
application, navigation, communication
avec le serveur.
➢ Réduire le code à écrire habituellement
requis en GWT.
➢ Optimisations: Proxy, code splitting...
GWTP
Partie 3 GWTP
31. ➢ Permet un découplage plus poussé et
rend le code plus modulaire (DIP, IoC).
○ Aide à atteindre de meilleures
pratiques,
○ Classes immutables,
○ Créer de nouvelles dépendances
devient plus simple : Extraction de
code vers des collaborateurs (SRP).
➢ Tend à rendre les tests plus simples:
Jukito, GuiceBerry…
➢ En bref : Aide à atteindre les principes
SOLID
Injection
de
dépendances
Partie 3 GWTP
32. ➢ Limiter le code redondant
à écrire qui est habituellement
requis en GWT.
➢ Séparer les phases d’initialisation
en phases logiques simple
à étendre ou remplacer.
➢ Permet d’utiliser l’injection
de dépendances plus tôt dans
le programme.
➢ Configuration initiale à un seul
endroit.
Références : DefaultModule, PreBootstrapper, Bootstrapper
Initialisation
de l’application
Partie 3 GWTP
33. Exemple: Initialisation et Bootstrapper
Configuration via le module GWT
public class ClientModule extends AbstractGinModule {
@Override
protected void configure() {
install(new DefaultModule.Builder()
.defaultPlace(NameTokens.LOGIN)
.errorPlace(NameTokens.LOGIN)
.unauthorizedPlace(NameTokens.UNAUTHORIZED)
.build());
install(new ApplicationModule());
bind(ResourceLoader.class).asEagerSingleton();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module>
<inherits name="com.gwtplatform.mvp.MvpWithEntryPoint"/>
<source path="client"/>
<source path="shared"/>
<set-configuration-property name="gwtp.bootstrapper"
value="com.arcbees.demo.client.DefaultBootstrapper"/>
<extend-configuration-property name="gin.ginjector.modules"
value="com.arcbees.demo.client.ClientModule"/>
</module>
Bootstrapper (facultatif!)
Configuration via GIN
public class DefaultBootstrapper implements Bootstrapper {
private final PlaceManager placeManager;
@Inject
DefaultBootstrapper(PlaceManager placeManager) {
this.placeManager = placeManager;
}
@Override
public void onBootstrap() {
// Initialize states then reveal the app:
placeManager.getCurrentPlaceRequest();
}
}
34. ➢ Permet de réagir à différents
stimuli provenant d’ailleurs
dans l’application.
➢ Permet d’éviter de coupler des
classes qui n’ont aucun point
commun.
➢ Implémentation générique
et de l’Observer.
Références : addRegisteredHandler, addVisibleHandler, @ProxyEvent
Plus de détails: https://blog.arcbees.com/?p=699
Évenements
(GWT, EventBus)
Partie 3 GWTP
35. ➢ Isoler le comportement
du presenter pour
chaque phase de sa vie.
➢ Comportements isolés
donc plus simple à
tester unitairement.
Lifecycle
(GWTP)
Références : onBind, onReveal, onReset, onHide,
onUnbind, prepareFromRequest
Partie 3 GWTP
37. ➢ Gère la navigation entre
les places. Beaucoup de code
à écrire autrement.
➢ Offre plusieurs façons de
présenter le token : le hash
dans l’URI, après le #.
➢ Possibilité de rediriger vers une
même page lorsqu’un token
inconnu est accédé. Page 404 à-
la GWTP.
Références : PlaceManager, PlaceRequest, TokenFormatter
Navigation
(GWTP)
Partie 3 GWTP
38. Exemple: Utiliser le Place Manager
Révéler un name token
private final PlaceManager placeManager;
@Inject
EditManufacturerPresenter(
EventBus eventBus,
MyView view,
PlaceManager placeManager) {
super(eventBus, view);
this.placeManager = placeManager;
}
@Override
public void cancel() {
PlaceRequest placeRequest = new PlaceRequest.Builder()
.nameToken(NameTokens.MANUFACTURERS)
.build();
placeManager.revealPlace(placeRequest);
}
Récupérer le name token courant
private final PlaceManager placeManager;
@Inject
HeaderPresenter(
EventBus eventBus,
MyView view,
PlaceManager placeManager) {
super(eventBus, view);
this.placeManager = placeManager;
}
@Override
protected void onReset() {
PlaceRequest currentPlace = placeManager.getCurrentPlaceRequest();
String currentNameToken = currentPlace.getNameToken();
getView().setActiveMenuItem(currentNameToken);
}
39. ➢ Permet de configurer et restreindre l’
accès à certains tokens.
➢ Possibilité de configurer un gatekeeper
par défaut.
➢ Permet de rediriger vers une même page
lorsque qu’un accès non autorisé est
détecté. Page 401 à-la GWTP.
➢ Attention: Si la page protégée par un
gatekeeper accède à des ressources
serveur, le serveur doit lui aussi protéger
ses ressources!
Références : Gatekeeper, GatekeeperWithParams, @UseGatekeeper,
@GatekeeperParams, @NoGatekeeper, @DefaultGatekeeper
Sécurité
(GWTP)
Partie 3 GWTP
40. Exemple: Utiliser un Gatekeeper
Gatekeeper
public abstract class AclGatekeeper implements GatekeeperWithParams {
private final CurrentSession currentSession;
private Set<String> permissions;
@Inject
AclGatekeeper(CurrentSession currentSession) {
this.currentSession = currentSession;
this.permissions = new HashSet<>();
}
@Override
public GatekeeperWithParams withParams(String[] params) {
permissions = new HashSet<>(Arrays.asList(params));
return this;
}
@Override
public boolean canReveal() {
return currentSession.isLoggedIn()
&& currentSession.hasPermissions(permissions);
}
}
Utilisation du Gatekeeper
public class EditGroupMembersPresenter
extends Presenter<MyView, MyProxy> {
interface MyView extends View {
}
@ProxyStandard
@NameToken(NameTokens.EDIT_GROUP_MEMBERS)
@UseGatekeeper(AclGatekeeper.class)
@GatekeeperParams({"GROUPS_UPDATE", "MEMBERS_UPDATE"})
interface MyProxy extends ProxyPlace<EditGroupMembersPresenter> {
}
/* snip */
}
41. ➢ Le coeur des fonctions de GWTP :
○ Accès au lifecycle,
○ Gestion centralisée des événements
○ Possède une vue,
○ Communication vue vers presenter via
un UiHandlers.
➢ Permet de développer
des composants réutilisables
et testables.
➢ Permet de définir des Slots
pour imbriquer d’autres PresenterWidgets.
Références : PresenterWidget, UiHandlers, View, OrderedSlot, SingleSlot, Slot
Presenter
Widgets
Partie 3 GWTP
42. Exemple: Un PresenterWidget
Le PresenterWidget
public class DashboardPresenter extends PresenterWidget<MyView> {
interface MyView extends View {
}
static final Slot<PresenterWidget<?>> SLOT_TEMPLATE = new SingleSlot<>();
private final Presenter<?, ?> presenter;
@Inject
DashboardPresenter(
EventBus eventBus,
MyView view,
Presenter<?, ?> presenter) {
super(eventBus, view);
this.presenter = presenter;
}
@Override
protected void onBind() {
setInSlot(SLOT_TEMPLATE, presenter);
}
}
Sa vue
public class DashboardView extends ViewImpl implements MyView {
interface Binder extends UiBinder<Widget, DashboardView> {
}
@UiField
SimplePanel template;
@Inject
DashboardView(Binder uiBinder) {
initWidget(uiBinder.createAndBindUi(this));
bindSlot(DashboardPresenter.SLOT_TEMPLATE, template);
}
}
Sa configuration GIN
public class DashboardModule extends AbstractPresenterModule {
@Override
protected void configure() {
bindPresenterWidget(DashboardPresenter.class,
DashboardPresenter.MyView.class, DashboardView.class);
}
}
43. ➢ Un PresenterWidget spécialisé.
➢ Doit être associé à un proxy.
➢ Initialisation uniquement lorsque
nécessaire: améliore le chargement initial.
➢ Peut être associé à un name token (place).
➢ Permet de définir des NestedSlots
pour imbriquer d’autres Presenters
sans couplage.
Références : Presenter, NestedSlot, Proxy, ProxyPlace, @ProxyStandard,
@NameToken
Presenter
Partie 3 GWTP
44. Exemple: Un Presenter
Le PresenterWidget
public class DashboardPresenter extends Presenter<MyView, MyProxy> {
interface MyView extends View {
}
@ProxyStandard
@NameToken(NameTokens.DASHBOARD)
@UseGatekeeper(LoggedInGatekeeper.class)
interface MyProxy extends ProxyPlace<DashboardPresenter> {
}
static final NestedSlot SLOT_TEMPLATE = new NestedSlot();
@Inject
DashboardPresenter(
EventBus eventBus,
MyView view,
MyProxy proxy) {
super(eventBus, view, proxy, ApplicationPresenter.SLOT_MAIN);
}
}
public class DashboardView extends ViewImpl implements MyView {
interface Binder extends UiBinder<Widget, DashboardView> {
}
@UiField
SimplePanel template;
@Inject
DashboardView(Binder uiBinder) {
initWidget(uiBinder.createAndBindUi(this));
bindSlot(DashboardPresenter.SLOT_TEMPLATE, template);
}
}
Sa configuration GIN
public class DashboardModule extends AbstractPresenterModule {
@Override
protected void configure() {
bindPresenter(DashboardPresenter.class,
DashboardPresenter.MyView.class, DashboardView.class,
DashboardPresenter.MyProxy.class);
}
}
Sa vue
45. ➢ Un PresenterWidget ayant un popup
comme vue.
➢ Les mêmes avantages qu’un
PresenterWidget: slots, lifecycle.
➢ Délègue l’affichage / masquage du popup
au presenter.
➢ Abstrait le concept de positionnement
du popup.
Références : PopupView, PopupViewImpl, addToPopupSlot, PopupPositioner
Popups
Partie 3 GWTP
47. Partie 4 Rest-Dispatch
Amené à disparaître dans GWT.
Nécessite beaucoup de code pour
un cas simple. Renforcit le
couplage entre client et serveur.
Plus simple de conserver
le code client et serveur
compatible.
RPC REST
Permet de communiquer avec n’
importe quel API REST.
Presque tous les serveurs
permettent de faire du REST :
à la mode. Plus facile de modifier l’
API et oublier de mettre le code
client à jour.
48. ➢ Implémentation partielle
de JSR-311 1.1 (JAX-RS).
➢ Définition d’interface et
configuration de l’API
au moyen d’annotations.
➢ Code client (GWT) seulement.
L’API doit être codé en utilisant
une autre technologie.
➢ Possibilité de créer ses propres
extensions.
Rest-Dispatch
Partie 4 Rest-Dispatch
49. ➢ Usage plus explicite que la syntaxe
de base.
➢ Les interfaces et annotations peuvent
être réutilisé sur l’implémentation
serveur (DRY).
➢ Plus simple de conserver un API privé
compatible à la fois sur le client
et le serveur.
➢ Désavantage: Perte du type safety
lors de la création de callbacks.
Extension :
Delegates
Partie 4 Rest-Dispatch
50. Exemple: Configurer et utiliser Rest-Dispatch
Le module GWT Les paths
Le module GIN L’interface de ressource
<?xml version='1.0' encoding='UTF-8'?>
<module>
<inherits
name="com.gwtplatform.dispatch.rest.delegates.ResourceDelegate"/>
<inherits name='com.gwtplatform.mvp.MvpWithEntryPoint'/>
<source path="client"/>
</module>
public class ResourcesModule extends AbstractGinModule {
@Override
protected void configure() {
install(new RestDispatchAsyncModule.Builder()
/* Additional configurations */
.build());
}
@Provides
@RestApplicationPath
String applicationPath() {
String baseUrl = GWT.getHostPageBaseURL();
if (baseUrl.endsWith("/"))
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
return baseUrl + API;
}
}
public static class DashboardPaths {
public static final String DASHBOARD_ID = "dashboard-id";
public static final String DASHBOARDS = "/dashboards";
public static final String DASHBOARD = "/{" + DASHBOARD_ID + "}";
}
@Path(DASHBOARDS)
public interface DashboardResource {
@GET
List<Dashboard> getAll();
@GET
@Path(DASHBOARD)
Dashboard get(@PathParam(DASHBOARD_ID) int id);
@PUT
@Path(DASHBOARD)
void update(@PathParam(DASHBOARD_ID) int id, Dashboard dashboard);
}
51. Exemple: Configurer et utiliser Rest-Dispatch (suite)
Utilisation de la ressource
static final SingleSlot<PresenterWidget<?>> SLOT_TEMPLATE = new SingleSlot<>();
private final ResourceDelegate<DashboardResource> dashboards;
private final DashboardTemplateFactory templateSelector;
@Inject
DashboardPresenter(
EventBus eventBus,
MyView view,
MyProxy proxy,
ResourceDelegate<DashboardResource> dashboards,
DashboardTemplateFactory templateFactory) {
super(eventBus, view, proxy, ApplicationPresenter.SLOT_MAIN);
this.dashboards = dashboardService;
this.templateFactory = templateFactory;
}
@Override
protected void onReveal() {
dashboards.withCallback(new AbstractAsyncCallback<Dashboard>() {
@Override
public void onSuccess(Dashboard result) {
PresenterWidget<?> template = templateFactory.createTemplate(result);
setInSlot(SLOT_TEMPLATE, template);
}
}).get(DashboardSettings.DEFAULT_DASHBOARD_ID);
}
53. Partie 5 Tests unitaires
Jukito
Jukito permet à vos tests d’utiliser l’
injection de dépendances. Peu
importe le type de test — unitaire,
intégration ou quoi que ce soit de
loufoque —, le code redondant dû
au mocking diminuera grandement.
Rapidement, vous ne pourrez plus
vous passez de sa syntaxe sécuritaire
et basée sur les annotations!
54. ➢ Mocking automatique
➢ Injection des mocks
➢ Annotation @ALL
➢ Léger
➢ S'adapte à toutes tailles de test
➢ Facile d’installation
Fonctionalités
Partie 5 Tests unitaires
55. Exemples
Sur GitHub
Mockito et Jukito:
ManufacturerDetailPresenter: https://goo.
gl/27NsKL
ManufacturerDetailPresenterTest: https://goo.
gl/irq5rV
ManufacturerDetailPresenterMockitoTest: https:
//goo.gl/EIlj47
Jukito @All:
PersonRenderer: https://goo.gl/o4UGZn
PersonRendererTest: https://goo.gl/uhcL2k
Partie 5 Tests unitaires
56. Mockito : Initialisation
CAS DE TEST TEST
@Inject
ManufacturerDetailPresenter(
EventBus eventBus,
MyView view,
MyProxy proxy,
ResourceDelegate<ManufacturersResource> manufacturers,
PlaceManager placeManager,
EditManufacturerMessages messages) {
super(eventBus, view, proxy, SLOT_MAIN_CONTENT);
this.manufacturers = manufacturers;
this.placeManager = placeManager;
this.messages = messages;
getView().setUiHandlers(this);
}
public class ManufacturerDetailPresenterMockitoTest {
// SUT
private ManufacturerDetailPresenter presenter;
// Mocks (created by Mockito)
@Mock private EventBus eventBus;
@Mock private MyView view;
@Mock private MyProxy proxy;
@Mock private ResourceDelegate<ManufacturersResource> manufacturers;
@Mock private PlaceManager placeManager;
@Before
public void setUp() {
// Create @Mock and @Captor fields.
MockitoAnnotations.initMocks(this);
// Manual way to create a mock
EditManufacturerMessages messages
= mock(EditManufacturerMessages.class);
// Manual creation of the SUT.
presenter = new ManufacturerDetailPresenter(eventBus, view, proxy,
manufacturers, placeManager, messages);
}
57. Mockito : Test
CAS DE TEST TEST
@Override
protected void onReveal() {
List<ActionType> actions;
if (createNew) {
actions = Arrays.asList(ActionType.DONE);
} else {
actions = Arrays.asList(ActionType.DELETE, ActionType.UPDATE);
}
ChangeActionBarEvent.fire(this, actions, false);
}
@Test
public void onReveal() {
// when
presenter.onReveal();
// then
// Manual way to create an argument captor
ArgumentCaptor<ChangeActionBarEvent> captor
= ArgumentCaptor.forClass(ChangeActionBarEvent.class);
verify(eventBus)
.fireEventFromSource(captor.capture(), same(presenter));
ChangeActionBarEvent event = captor.getValue();
assertThat(event).isNotNull();
assertThat(event.getActions()).containsOnly(ActionType.DONE);
assertThat(event.getTabsVisible()).isFalse();
}
58. Jukito : Initialisation
CAS DE TEST TEST
@RunWith(JukitoRunner.class)
public class ManufacturerDetailPresenterTest {
// SUT (Injected by Jukito). We don't need to create all dependencies
explicitly, Jukito will mock them.
@Inject
private ManufacturerDetailPresenter presenter;
// Mocks (injected by Jukito)
@Inject
private EventBus eventBus;
// Captors (Create by Mockito)
@Captor
private ArgumentCaptor<ChangeActionBarEvent> changeActionBarEventCaptor;
@Before
public void setUp() {
// Create @Mock and @Captor fields.
MockitoAnnotations.initMocks(this);
}
@Inject
ManufacturerDetailPresenter(
EventBus eventBus,
MyView view,
MyProxy proxy,
ResourceDelegate<ManufacturersResource> manufacturers,
PlaceManager placeManager,
EditManufacturerMessages messages) {
super(eventBus, view, proxy, SLOT_MAIN_CONTENT);
this.manufacturers = manufacturers;
this.placeManager = placeManager;
this.messages = messages;
getView().setUiHandlers(this);
}
59. Jukito : Test
CAS DE TEST TEST
@Override
protected void onReveal() {
List<ActionType> actions;
if (createNew) {
actions = Arrays.asList(ActionType.DONE);
} else {
actions = Arrays.asList(ActionType.DELETE, ActionType.UPDATE);
}
ChangeActionBarEvent.fire(this, actions, false);
}
@Test
public void onReveal_newManufacturer_preparesActionBar() {
// when
presenter.onReveal();
// then
verify(eventBus)
.fireEventFromSource(changeActionBarEventCaptor.capture(),
same(presenter));
ChangeActionBarEvent event = changeActionBarEventCaptor.getValue();
assertThat(event).isNotNull();
assertThat(event.getActions()).containsOnly(ActionType.DONE);
assertThat(event.getTabsVisible()).isFalse();
}
60. Jukito : Utiliser @All pour tester plusieurs cas
CAS DE TEST TEST
@Inject
PersonRenderer(
Messages messages,
@Assisted Mode mode) {
this.mode = mode;
this.messages = messages;
}
public String render(Person person) {
String result = "";
if (person != null) {
String firstName = person.getFirstName();
String middleName = person.getMiddleName();
String lastName = person.getLastName();
String title = renderTitle(person);
if (mode.isDisplayTitle() && !Strings.isNullOrEmpty(title)) {
result += title + " ";
}
if (mode.isDisplayFirstName() && !Strings.isNullOrEmpty(firstName)) {
result += firstName + " ";
}
if (mode.isDisplayMiddleName() && !Strings.isNullOrEmpty(middleName)) {
result += middleName + " ";
}
if (mode.isDisplayLastName() && !Strings.isNullOrEmpty(lastName)) {
result += lastName;
}
}
if (result.isEmpty()) {
result = messages.unknown();
}
return result;
}
public class PersonNameTestCase {
private final Person person;
private Mode mode;
private String expected;
public PersonNameTestCase() {
person = null;
}
public PersonNameTestCase(
String firstName,
String lastName) {
person = new Person(firstName, lastName);
}
/* Setters */
public Person getPerson() {
return person;
}
public Mode getMode() {
return mode;
}
public String getExpected() {
return expected;
}
}
61. Jukito : Utiliser @All pour tester plusieurs cas (suite)
TEST TEST
@RunWith(JukitoRunner.class)
public class PersonRendererTest {
public static class Module extends JukitoModule {
@Override
protected void configureTest() {
bindManyInstances(PersonNameTestCase.class,
new PersonNameTestCase("Zom", "Bee")
.mode(Mode.SHORT).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee").title(Title.MR)
.mode(Mode.SHORT).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee").middleName("Buzz")
.mode(Mode.SHORT).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee").title(Title.MR).middleName("Buzz")
.mode(Mode.SHORT).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee")
.mode(Mode.FORMAL).expected("Zom Bee"),
new PersonNameTestCase("Zom", "Bee").title(Title.MS)
.mode(Mode.FORMAL).expected("Ms. Zom Bee"),
new PersonNameTestCase("Zom", "Bee").middleName("Buzz")
.mode(Mode.FORMAL).expected("Zom Buzz Bee"),
new PersonNameTestCase("Zom", "Bee").title(Title.MS).middleName("Buzz")
.mode(Mode.FORMAL).expected("Ms. Zom Buzz Bee"),
new PersonNameTestCase()
.mode(Mode.SHORT).expected(UNKNOWN),
new PersonNameTestCase()
.mode(Mode.FORMAL).expected(UNKNOWN)
);
}
}
private static final String UNKNOWN = "unknown";
@Inject
private Messages messages;
@Before
public void setUp() {
given(messages.unknown()).willReturn(UNKNOWN);
given(messages.title(Title.MR)).willReturn("Mr.");
given(messages.title(Title.MS)).willReturn("Ms.");
}
@Test
public void render(@All PersonNameTestCase testCase) {
// given
Person person = testCase.getPerson();
Mode mode = testCase.getMode();
PersonRenderer renderer = new PersonRenderer(messages, mode);
// when
String result = renderer.render(person);
// then
String expected = testCase.getExpected();
assertThat(result).overridingErrorMessage(
"Expected %s with mode %s to be '%s'.", person, mode, expected)
.isEqualTo(expected);
}
62. Jukito : Utilitaires pour tester Rest-Dispatch
CAS DE TEST TEST
public void onSave(ManufacturerDto manufacturer) {
manufacturersDelegate
.withCallback(new AbstractAsyncCallback<ManufacturerDto>(this) {
@Override
public void onSuccess(ManufacturerDto newManufacturer) {
DisplayMessageEvent.fire(ManufacturerDetailPresenter.this,
new Message(messages.manufacturerSaved(), SUCCESS));
PlaceRequest placeRequest = new Builder()
.nameToken(NameTokens.MANUFACTURER)
.build();
placeManager.revealPlace(placeRequest);
}
})
.saveOrCreate(manufacturerDto);
}
@Test
public void onSave_showsMessage_revealsManufacturers(
EditManufacturerMessages messages) {
// given
ManufacturerDto manufacturer = new ManufacturerDto();
ManufacturerDto resultManufacturer = new ManufacturerDto();
givenDelegate(manufacturersResource)
.succeed().withResult(resultManufacturer)
.when().saveOrCreate(same(manufacturer));
given(messages.manufacturerSaved()).willReturn(A_MESSAGE);
// when
presenter.onSave(manufacturer);
// then
// note `isA` is used instead of `any`. This is because the event bus
// accepts all `GwtEvent` subclasses. `isA` also verifies the type
verify(eventBus)
.fireEventFromSource(isA(DisplayMessageEvent.class),same(presenter));
verify(placeManager).revealPlace(placeRequestCaptor.capture());
PlaceRequest placeRequest = placeRequestCaptor.getValue();
assertThat(placeRequest.getNameToken())
.isEqualTo(NameTokens.MANUFACTURER);
}
72. Even if you don’t need it
Full Event
Mechanism
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
73. How to attach
event handler
to elements ?
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
74.
75. Use
widget
To encapsulate complex
component to reuse
» prefer PresenterWidget if the component
has a lot of business logic
» In the futur: Web Component.
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6
76. Cell widgets (CellTable, CellList…)
HtmlPanel
Exceptions
BP 1 : AVOID WIDGETS AS MUCH AS YOU CAN
Partie 6