SlideShare una empresa de Scribd logo
1 de 27
Descargar para leer sin conexión
Testing GWT Applications
Erik Kuefler, Google

San Francisco, December 12-13th 2013
Google Confidential and Proprietary
Two Types of Tests
● Unit tests
● Test classes, methods and APIs
● Quick and focused
● Enable rapid iteration
● Functional tests
● Test applications and UIs
● Thorough and resilient
● Provide end-to-end confidence
Google Confidential and Proprietary
Unit Tests

Google Confidential and Proprietary
Unit Tests
● Test a single class or set of closely-related classes
● Mock out heavyweight dependencies
● Run very quickly

Google Confidential and Proprietary
Should I Use GWTTestCase?
● Generally not as a first choice
● Compiling to javascript is sloooow
● Standard Java tools aren't available*
● Prefer to test code directly as Java when possible
● Still useful for testing JSNI and heavy DOM work
*Check out EasyGwtMock though
Google Confidential and Proprietary
class EmailField extends Composite {
@UiField HasText textBox, message;
private final EmailServiceAsync service = GWT.create(EmailService.class);
@UiHandler("saveButton") void onSaveClicked(ClickEvent e) {
if (RegExp.compile("[a-z]*@[a-z]*.com").test(textBox.getText())) {
service.setEmail(textBox.getText(), new AsyncCallback<Void>() {
@Override void onSuccess(Void result) {
message.setText("Success!");
}
@Override void onFailure(Throwable t) {
message.setText("Error: " + t.getMessage());
}
});
} // Else show an error...
}
}

Google Confidential and Proprietary
class EmailField extends Composite {
@UiField HasText textBox, message;

How can I get instances of widgets?

private final EmailServiceAsync service = GWT.create(EmailService.class);
How can I emulate a button click?
How can I create a fake service?
@UiHandler("saveButton") void onSaveClicked(ClickEvent e) {
How can if (RegExp.compile("[a-z]*@[a-z]*.com").test(textBox.getText())) {
I verify
service.setEmail(textBox.getText(), new AsyncCallback<Void>() {
that a
@Override void onSuccess(Void result) {
service
How can I set a value in a text box?
was
message.setText("Success!");
invoked
How can I fake responses from the service?
}
(or not)?
@Override void onFailure(Throwable t) {
message.setText("Error: " + t.getMessage());
}
});

How can I check changes to the DOM?

} // Else show an error...
}
}

Google Confidential and Proprietary
The Solution: (Gwt)Mockito
● We need a way to create a fake server and browser
● Mockito is a great library for generating mock objects
● GwtMockito automatically mocks GWT constructs
● No need to factor out an MVP-style view!

Google Confidential and Proprietary
@RunWith(GwtMockitoTestRunner.class)
public class EmailFieldTest {
private final EmailField field = new EmailField();
@GwtMock private EmailServiceAsync service;
@Test public void shouldSaveWellFormedAddresses() {
when(field.textBox.getText()).thenReturn("me@foo.com");
doAnswer(asyncSuccess()).when(service).setEmail(
anyString(), anyCallback());
field.onSaveClicked(new ClickEvent() {});
verify(field.message).setText("Success!");
}
@Test public void shouldNotSaveMalformedAddresses() {
when(field.textBox.getText()).thenReturn("bademail");
field.onSaveClicked(new ClickEvent() {});
verify(service, never()).setEmail(anyString(), anyCallback());
}
}

Google Confidential and Proprietary
@RunWith(GwtMockitoTestRunner.class)
public class EmailFieldTest {

Magical GWT-aware test runner

private final EmailField field = new EmailField();
@GwtMock private EmailServiceAsync service;

Reference the result of GWT.create
@Test public void shouldSaveWellFormedAddresses() {
when(field.textBox.getText()).thenReturn("me@foo.com");
doAnswer(asyncSuccess()).when(service).setEmail(
anyString(), anyCallback());Mock service can be programmed to return
field.onSaveClicked(new ClickEvent() {});
success or failure
verify(field.message).setText("Success!");
}

Package-private UiFields are filled with mocks
@Test public void shouldNotSaveMalformedAddresses() {
when(field.textBox.getText()).thenReturn("bademail"); UiHandlers can be called
field.onSaveClicked(new ClickEvent() {});

directly (note the {})
verify(service, never()).setEmail(anyString(), anyCallback());
}
}

Google Confidential and Proprietary
Dealing With JSNI
● GwtMockito stubs native methods to be no-ops
returning "harmless" values
● What to do when a no-op isn't enough?
● Best choice: dependency injection
● Last resort: overriding in tests
private boolean calledDoJavascript = false;
private MyWidget widget = new MyWidget() {
@Override void doJavascript() { calledDoJavascript = true;}
};

● Fall back to GWTTestCase to test the actual JS
Google Confidential and Proprietary
GwtMockito Summary
● Install via @RunWith(GwtMockitoTestRunner.class)
● Causes calls to GWT.create to return mocks or fakes
● Creates fake UiBinders that fill @UiFields with mocks
● Replaces JSNI methods with no-ops
● Removes final modifiers
Google Confidential and Proprietary
Functional Tests

Google Confidential and Proprietary
Functional tests
● Selenium/Webdriver tests that act like a user
● Provide implementation-independent tests
● Use either real or fake servers
● Appropriate for use-case-driven testing

Google Confidential and Proprietary
Page Objects
● Provide a user-focused API for interacting with a widget
● Usually map 1:1 to GWT widgets
● Can contain other page objects
● All page object methods return one of:
● The page object itself (when there is no transition)
● Another page object (when there is a transition)
● A user-visible value from the UI (for assertions)

Google Confidential and Proprietary
Google Confidential and Proprietary
Google Confidential and Proprietary
Google Confidential and Proprietary
public class AddCreditCardPage {
private final String id;
public AddCreditCardPage fillCreditCardNumber(String number) {
wait().until(presenceOfElementLocated(By.id(id + Ids.CARD_NUMBER))
.sendKeys(number);
return this;

Wait for elements to be ready

}
public ReviewPage clickAddCreditCardButton() {
wait().until(elementToBeClickable(By.id(id + Ids.ADD_CREDIT_CARD)))
.click();
return new ReviewPage(Ids.REVIEW_PURCHASE);
}

Change pages by returning a
new page object

public String getErrorMessage() {
return wait().until(presenceOfElementLocated(By.id(id + Ids.ERROR))
.getText();
}

Always reference by ID

}

Google Confidential and Proprietary
Using Page Objects
@Test public void shouldSelectCardsAfterAddingThem() {
String selectedCard = new MerchantPage(webDriver) // Returns MerchantPage
.clickBuy()

// Returns ReviewPage

.openCreditCardSelector()

// Returns SelectorPage

.selectAddCreditCardOption()

// Returns AddCardPage

.fillCreditCardNumber("4111111111110123")

// Returns AddCardPage

.fillCvc("456")

// Returns AddCardPage

.clickAddCreditCardButton()

// Returns ReviewPage

.openCreditCardSelector()
.getSelectedItem();

// Returns SelectorPage
// Returns String

assertEquals("VISA 0123", selectedCard);
}

Note that the test never uses
WebDriver/Selenium APIS!
Google Confidential and Proprietary
Referring to Elements
● Page objects always reference elements by ID
● IDs are defined hierarchically: each level gives a new
widget or page object
●

Example ID: ".buyPage.creditCardForm.billingAddress.zip"

●

Created via concatenation: find(By.id(myId + childId));

● Page objects should never refer to grandchildren
● IDs set via ensureDebugId can be disabled in prod
Google Confidential and Proprietary
Configuring IDs in GWT
public class CreditCardFormWidget extends Composite {
@Override protected void onEnsureDebugId(String baseId) {
super.onEnsureDebugId(baseId);
creditCardNumber.ensureDebugId(baseId + Ids.CARD_NUMBER);
addressFormWidget.ensureDebugId(baseId + Ids.BILLING_ADDRESS);
}
}

Shared between prod GWT
code and test code

public class Ids {
public static final String CREDIT_CARD_FORM = ".ccForm";
public static final String CARD_NUMBER = ".ccNumber";
public static final String BILLING_ADDRESS = ".billingAddress";
}

Google Confidential and Proprietary
Stubbing Servers
public class RealServerConnection implements ServerConnection {
@Override public void sendRequest(
String url, String data, Callback callback) {
RequestBuilder request = new RequestBuilder(RequestBuilder.POST, url);
request.sendRequest(data, callback);
}
}
public class StubServerConnection implements ServerConnection {
@Override public native void sendRequest(
String url, String data, Callback callback) /*-{
callback.Callback::onSuccess(Ljava/lang/String;)($wnd.stubData[url]);
}-*/;
}

Read a canned response from a
Javascript variable

Google Confidential and Proprietary
Setting Up Deferred Binding
<define-property name="serverType" values="real,stub"/>
<set-property name="serverType" value="real"/>
<replace-with class="my.package.RealServerConnection">
<when-type-is class="my.package.ServerConnection"/>
<when-property-is name="serverType" value="real"/>
</replace-with>
<replace-with class="my.package.StubServerConnection">
<when-type-is class="my.package.ServerConnection"/>
<when-property-is name="serverType" value="stub"/>
</replace-with>

Google Confidential and Proprietary
Setting Up Stubs In Tests
@Test public void shouldShowContactsInRecipientAutocomplete() {
new StubServer(webDriver).setContactData("John Doe", "Jane Doe", "Bob");
List<String> suggestions = new EmailPage(webDriver)
.clickSendEmail()
.setRecipients("Doe")
.getAutocompleteSuggestions();
assertEquals(2, suggestions.size());
assertContains("John Doe", suggestions);
assertContains("Jane Doe", suggestions);
}
public void setContactData(String... data) {
((JavascriptExecutor) webDriver).executeScript(
"stubData['get_contact_data'] = arguments", data);
}
Google Confidential and Proprietary
Tips For Functional Tests
● Always use IDs, never xpath
● Wait, don't assert (or sleep)
●

See org.openqa.selenium.support.ui.ExpectedConditions

● Never reference grandchildren in page objects
● Never use WebDriver APIs directly in tests
● Expose javascript APIs to functional tests judiciously

Google Confidential and Proprietary
Please rate this presentation at gwtcreate.com/agenda!

Q&A
ekuefler@google.com
https://github.com/ekuefler
+Erik Kuefler
Mockito: http://code.google.com/p/mockito/
GwtMockito: https://github.com/google/gwtmockito
EasyGwtMock: https://code.google.com/p/easy-gwt-mock
WebDriver: http://www.seleniumhq.org/projects/webdriver/

Google Confidential and Proprietary

Más contenido relacionado

Destacado

Geusseltsport dames sponsors_V2
Geusseltsport dames sponsors_V2Geusseltsport dames sponsors_V2
Geusseltsport dames sponsors_V2Miriam Monfrance
 
학습촉진
학습촉진학습촉진
학습촉진예슬 이
 
단발머리머리머리멀미
단발머리머리머리멀미단발머리머리머리멀미
단발머리머리머리멀미예슬 이
 
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.Mari Artazkoz Ardanaz
 
Geusseltsport dames Sponsors
Geusseltsport dames SponsorsGeusseltsport dames Sponsors
Geusseltsport dames SponsorsMiriam Monfrance
 
Ppt multimedia pembelajaran bi
Ppt multimedia pembelajaran biPpt multimedia pembelajaran bi
Ppt multimedia pembelajaran biomegha
 

Destacado (11)

Geusseltsport dames sponsors_V2
Geusseltsport dames sponsors_V2Geusseltsport dames sponsors_V2
Geusseltsport dames sponsors_V2
 
학습촉진
학습촉진학습촉진
학습촉진
 
SEO Roadmap
SEO RoadmapSEO Roadmap
SEO Roadmap
 
단발머리머리머리멀미
단발머리머리머리멀미단발머리머리머리멀미
단발머리머리머리멀미
 
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.
Txorimaloa. Lehenengo zikloko haurrek egindako ipuina.
 
Geusseltsport dames Sponsors
Geusseltsport dames SponsorsGeusseltsport dames Sponsors
Geusseltsport dames Sponsors
 
tes
testes
tes
 
International Success Package
International Success PackageInternational Success Package
International Success Package
 
Health safety
Health safetyHealth safety
Health safety
 
Bf restaurant
Bf restaurantBf restaurant
Bf restaurant
 
Ppt multimedia pembelajaran bi
Ppt multimedia pembelajaran biPpt multimedia pembelajaran bi
Ppt multimedia pembelajaran bi
 

Último

MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsNanddeep Nachan
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...apidays
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamUiPathCommunity
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistandanishmna97
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Angeliki Cooney
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelDeepika Singh
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...apidays
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusZilliz
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDropbox
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 

Último (20)

MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 

Testing GWT Applications

  • 1. Testing GWT Applications Erik Kuefler, Google San Francisco, December 12-13th 2013 Google Confidential and Proprietary
  • 2. Two Types of Tests ● Unit tests ● Test classes, methods and APIs ● Quick and focused ● Enable rapid iteration ● Functional tests ● Test applications and UIs ● Thorough and resilient ● Provide end-to-end confidence Google Confidential and Proprietary
  • 4. Unit Tests ● Test a single class or set of closely-related classes ● Mock out heavyweight dependencies ● Run very quickly Google Confidential and Proprietary
  • 5. Should I Use GWTTestCase? ● Generally not as a first choice ● Compiling to javascript is sloooow ● Standard Java tools aren't available* ● Prefer to test code directly as Java when possible ● Still useful for testing JSNI and heavy DOM work *Check out EasyGwtMock though Google Confidential and Proprietary
  • 6. class EmailField extends Composite { @UiField HasText textBox, message; private final EmailServiceAsync service = GWT.create(EmailService.class); @UiHandler("saveButton") void onSaveClicked(ClickEvent e) { if (RegExp.compile("[a-z]*@[a-z]*.com").test(textBox.getText())) { service.setEmail(textBox.getText(), new AsyncCallback<Void>() { @Override void onSuccess(Void result) { message.setText("Success!"); } @Override void onFailure(Throwable t) { message.setText("Error: " + t.getMessage()); } }); } // Else show an error... } } Google Confidential and Proprietary
  • 7. class EmailField extends Composite { @UiField HasText textBox, message; How can I get instances of widgets? private final EmailServiceAsync service = GWT.create(EmailService.class); How can I emulate a button click? How can I create a fake service? @UiHandler("saveButton") void onSaveClicked(ClickEvent e) { How can if (RegExp.compile("[a-z]*@[a-z]*.com").test(textBox.getText())) { I verify service.setEmail(textBox.getText(), new AsyncCallback<Void>() { that a @Override void onSuccess(Void result) { service How can I set a value in a text box? was message.setText("Success!"); invoked How can I fake responses from the service? } (or not)? @Override void onFailure(Throwable t) { message.setText("Error: " + t.getMessage()); } }); How can I check changes to the DOM? } // Else show an error... } } Google Confidential and Proprietary
  • 8. The Solution: (Gwt)Mockito ● We need a way to create a fake server and browser ● Mockito is a great library for generating mock objects ● GwtMockito automatically mocks GWT constructs ● No need to factor out an MVP-style view! Google Confidential and Proprietary
  • 9. @RunWith(GwtMockitoTestRunner.class) public class EmailFieldTest { private final EmailField field = new EmailField(); @GwtMock private EmailServiceAsync service; @Test public void shouldSaveWellFormedAddresses() { when(field.textBox.getText()).thenReturn("me@foo.com"); doAnswer(asyncSuccess()).when(service).setEmail( anyString(), anyCallback()); field.onSaveClicked(new ClickEvent() {}); verify(field.message).setText("Success!"); } @Test public void shouldNotSaveMalformedAddresses() { when(field.textBox.getText()).thenReturn("bademail"); field.onSaveClicked(new ClickEvent() {}); verify(service, never()).setEmail(anyString(), anyCallback()); } } Google Confidential and Proprietary
  • 10. @RunWith(GwtMockitoTestRunner.class) public class EmailFieldTest { Magical GWT-aware test runner private final EmailField field = new EmailField(); @GwtMock private EmailServiceAsync service; Reference the result of GWT.create @Test public void shouldSaveWellFormedAddresses() { when(field.textBox.getText()).thenReturn("me@foo.com"); doAnswer(asyncSuccess()).when(service).setEmail( anyString(), anyCallback());Mock service can be programmed to return field.onSaveClicked(new ClickEvent() {}); success or failure verify(field.message).setText("Success!"); } Package-private UiFields are filled with mocks @Test public void shouldNotSaveMalformedAddresses() { when(field.textBox.getText()).thenReturn("bademail"); UiHandlers can be called field.onSaveClicked(new ClickEvent() {}); directly (note the {}) verify(service, never()).setEmail(anyString(), anyCallback()); } } Google Confidential and Proprietary
  • 11. Dealing With JSNI ● GwtMockito stubs native methods to be no-ops returning "harmless" values ● What to do when a no-op isn't enough? ● Best choice: dependency injection ● Last resort: overriding in tests private boolean calledDoJavascript = false; private MyWidget widget = new MyWidget() { @Override void doJavascript() { calledDoJavascript = true;} }; ● Fall back to GWTTestCase to test the actual JS Google Confidential and Proprietary
  • 12. GwtMockito Summary ● Install via @RunWith(GwtMockitoTestRunner.class) ● Causes calls to GWT.create to return mocks or fakes ● Creates fake UiBinders that fill @UiFields with mocks ● Replaces JSNI methods with no-ops ● Removes final modifiers Google Confidential and Proprietary
  • 14. Functional tests ● Selenium/Webdriver tests that act like a user ● Provide implementation-independent tests ● Use either real or fake servers ● Appropriate for use-case-driven testing Google Confidential and Proprietary
  • 15. Page Objects ● Provide a user-focused API for interacting with a widget ● Usually map 1:1 to GWT widgets ● Can contain other page objects ● All page object methods return one of: ● The page object itself (when there is no transition) ● Another page object (when there is a transition) ● A user-visible value from the UI (for assertions) Google Confidential and Proprietary
  • 16. Google Confidential and Proprietary
  • 17. Google Confidential and Proprietary
  • 18. Google Confidential and Proprietary
  • 19. public class AddCreditCardPage { private final String id; public AddCreditCardPage fillCreditCardNumber(String number) { wait().until(presenceOfElementLocated(By.id(id + Ids.CARD_NUMBER)) .sendKeys(number); return this; Wait for elements to be ready } public ReviewPage clickAddCreditCardButton() { wait().until(elementToBeClickable(By.id(id + Ids.ADD_CREDIT_CARD))) .click(); return new ReviewPage(Ids.REVIEW_PURCHASE); } Change pages by returning a new page object public String getErrorMessage() { return wait().until(presenceOfElementLocated(By.id(id + Ids.ERROR)) .getText(); } Always reference by ID } Google Confidential and Proprietary
  • 20. Using Page Objects @Test public void shouldSelectCardsAfterAddingThem() { String selectedCard = new MerchantPage(webDriver) // Returns MerchantPage .clickBuy() // Returns ReviewPage .openCreditCardSelector() // Returns SelectorPage .selectAddCreditCardOption() // Returns AddCardPage .fillCreditCardNumber("4111111111110123") // Returns AddCardPage .fillCvc("456") // Returns AddCardPage .clickAddCreditCardButton() // Returns ReviewPage .openCreditCardSelector() .getSelectedItem(); // Returns SelectorPage // Returns String assertEquals("VISA 0123", selectedCard); } Note that the test never uses WebDriver/Selenium APIS! Google Confidential and Proprietary
  • 21. Referring to Elements ● Page objects always reference elements by ID ● IDs are defined hierarchically: each level gives a new widget or page object ● Example ID: ".buyPage.creditCardForm.billingAddress.zip" ● Created via concatenation: find(By.id(myId + childId)); ● Page objects should never refer to grandchildren ● IDs set via ensureDebugId can be disabled in prod Google Confidential and Proprietary
  • 22. Configuring IDs in GWT public class CreditCardFormWidget extends Composite { @Override protected void onEnsureDebugId(String baseId) { super.onEnsureDebugId(baseId); creditCardNumber.ensureDebugId(baseId + Ids.CARD_NUMBER); addressFormWidget.ensureDebugId(baseId + Ids.BILLING_ADDRESS); } } Shared between prod GWT code and test code public class Ids { public static final String CREDIT_CARD_FORM = ".ccForm"; public static final String CARD_NUMBER = ".ccNumber"; public static final String BILLING_ADDRESS = ".billingAddress"; } Google Confidential and Proprietary
  • 23. Stubbing Servers public class RealServerConnection implements ServerConnection { @Override public void sendRequest( String url, String data, Callback callback) { RequestBuilder request = new RequestBuilder(RequestBuilder.POST, url); request.sendRequest(data, callback); } } public class StubServerConnection implements ServerConnection { @Override public native void sendRequest( String url, String data, Callback callback) /*-{ callback.Callback::onSuccess(Ljava/lang/String;)($wnd.stubData[url]); }-*/; } Read a canned response from a Javascript variable Google Confidential and Proprietary
  • 24. Setting Up Deferred Binding <define-property name="serverType" values="real,stub"/> <set-property name="serverType" value="real"/> <replace-with class="my.package.RealServerConnection"> <when-type-is class="my.package.ServerConnection"/> <when-property-is name="serverType" value="real"/> </replace-with> <replace-with class="my.package.StubServerConnection"> <when-type-is class="my.package.ServerConnection"/> <when-property-is name="serverType" value="stub"/> </replace-with> Google Confidential and Proprietary
  • 25. Setting Up Stubs In Tests @Test public void shouldShowContactsInRecipientAutocomplete() { new StubServer(webDriver).setContactData("John Doe", "Jane Doe", "Bob"); List<String> suggestions = new EmailPage(webDriver) .clickSendEmail() .setRecipients("Doe") .getAutocompleteSuggestions(); assertEquals(2, suggestions.size()); assertContains("John Doe", suggestions); assertContains("Jane Doe", suggestions); } public void setContactData(String... data) { ((JavascriptExecutor) webDriver).executeScript( "stubData['get_contact_data'] = arguments", data); } Google Confidential and Proprietary
  • 26. Tips For Functional Tests ● Always use IDs, never xpath ● Wait, don't assert (or sleep) ● See org.openqa.selenium.support.ui.ExpectedConditions ● Never reference grandchildren in page objects ● Never use WebDriver APIs directly in tests ● Expose javascript APIs to functional tests judiciously Google Confidential and Proprietary
  • 27. Please rate this presentation at gwtcreate.com/agenda! Q&A ekuefler@google.com https://github.com/ekuefler +Erik Kuefler Mockito: http://code.google.com/p/mockito/ GwtMockito: https://github.com/google/gwtmockito EasyGwtMock: https://code.google.com/p/easy-gwt-mock WebDriver: http://www.seleniumhq.org/projects/webdriver/ Google Confidential and Proprietary