-
Notifications
You must be signed in to change notification settings - Fork 0
Writing CSV scenarios
Coding complex user use case can be boring and... complex, depending on the used Web technology.
That's why gwt-test-utils comes with features to make it easy for GWT : it provides a CSV human-readable grammar to simule user browser actions and to validate results. It also enable to connect real server backends to do real integration tests.
To start writing integration tests with the CSV grammar, you will need to add the gwt-test-utils-csv JAR file in the test classpath, just like explained in the Setup page.
First, let's write a combinaison of browser actions on a simple UI, with no server call.
The main EntryPoint
for our simple GWT module :
public class CsvSample implements EntryPoint {
private RpcSampleView rpcSampleView;
public void onModuleLoad() {
rpcSampleView = new RpcSampleView();
RootPanel.get().add(rpcSampleView);
}
}
The added RpcSampleView
is exactly the same as in our mocking RPC sample.
To run tests, you'll need a runner class which can tell were to find CSV test files to execute and how to start the GWT UI without the GWT HostedMode. GwtCsvTest
is the base class you'll need to extend to do so.
Since the GreetingServiceImpl
use the servlet API, steps explained in the Mocking Servlets wiki page must also be applied :
@GwtModule("com.googlecode.gwt.test.sample.CsvSample")
@CsvDirectory(value = "functional-tests", extension = ".csv")
public class CsvSampleTest extends GwtCsvTest {
private CsvSample app;
/** Servlet API mock helpers from gwt-test-utils **/
private MockServletConfig mockConfig;
private MockHttpServletRequest mockRequest;
@Before
public void before() {
// create the ServletConfig object using gwt-test-utils web mock helper
MockServletContext context = new MockServletContext();
context.setServerInfo("mocked-server-info");
this.mockConfig = new MockServletConfig(context);
// same thing for HttpServletRequest
this.mockRequest = new MockHttpServletRequest();
this.mockRequest.addHeader("User-Agent", "mocked-user-agent");
// use the provided adapter to implement only the methods you need for your test
setServletMockProvider(new ServletMockProviderAdapter() {
@Override
public ServletConfig getMockedConfig(AbstractRemoteServiceServlet remoteService) {
return mockConfig;
}
@Override
public HttpServletRequest getMockedRequest(AbstractRemoteServiceServlet rpcService, Method rpcMethod) {
return mockRequest;
}
});
GwtFinder.registerNodeFinder("app", new NodeObjectFinder() {
public Object find(Node node) {
return csvRunner.getNodeValue(app, node);
}
});
GwtFinder.registerNodeFinder("rpcSampleView", new NodeObjectFinder() {
public Object find(Node node) {
return csvRunner.getNodeValue(RootPanel.get().getWidget(0), node);
}
});
}
@CsvMethod
public void initApp() {
app = new CsvSample();
app.onModuleLoad();
}
}
-
The
@CsvDirectory
class annotation tells gwt-test-utils where to find CSV files. In this case, all files with the extension .csv in the project folder 'functional-tests' in project root directory will be loaded. The 'extension' property is optional and equals ".csv" by default. -
The
initApp
method initializes the application. This method is annoted with a markup annotation :@CsvMethod
, which tells the frameworkinitApp
is a fixture which can be invoked in CSV tests. -
Finally, some custom
NodeObjectFinder
have been registered to theGwtFinder
helper object described here. Those finders relies on the inheritedCsvRunner
instance to provide cool ways to access the UI components.
As for standard GwtTest, a META-INF/gwt-test-utils.properties
file is required in the test classpath. It must declare all modules under test :
com.googlecode.gwt.test.sample.CsvSample = gwt-module
Now, you can write your first CSV senario which will use GwtCsvTest
fixtures. Create a functional-tests/basic-test.csv
file :
A complete scenario to validate all RpcSampleView behaviors
start
** starts the CsvSample EntryPoint
initApp
** pre-assertions
isNotVisible;/rpcSampleView/label
assertText;*empty*;/rpcSampleView/textBox
isNotEnabled;/rpcSampleView/button
** action 1
fillTextBox;123;/rpcSampleView/textBox
click;/rpcSampleView/button
** assertions 1
isVisible;/rpcSampleView/label
assertText;Server error: Name must be at least 4 characters long;/rpcSampleView/label
** action 2
fillTextBox;test CSV;/rpcSampleView/textBox
click;/rpcSampleView/button
** assertions 2
isVisible;/rpcSampleView/label
assertText;Hello, test CSV!<br><br>I am running mocked-server-info.<br><br>It looks like you are using:<br>mocked-user-agent;/rpcSampleView/label
- First, the CSV scenario is launched with the
start
inscruction. Every line before this start would be considere as non exectuable. - Than, the
@CsvMethod CsvSampleTest.initApp()
fixture is invoked to instanciate the GWT application.
Since the application is initialized, you can write your action/assertions to validate the UI behaviors. Let's explain the first line :
isNotVisible;/rpcSampleView/label
-
The first CSV cell desribes the
@CsvMethod
fixture to invoke.GwtCsvTest
provides a lot of default fixtures, such asassertNotExist
,assertExact
assertText
,isEnabled
,click
,fillText
and so on. -
Other cells are the action arguments. In that case, the
GwtCsvTest.isNotVisible(String... identifier)
requiers the object identifier in the UI. You can read more on how to access widget here.
Text assertions can be written in may ways :
assertContains;Hello, test CSV!;/root/widget(0)/label/text
assertContains;Hello, test CSV!;/app/rpcSampleView/label/text
assertContains;Hello, test CSV!;/rpcSampleView/label/text
assertContains;Hello, test CSV!;/rpcSampleView/label
Those 4 lines check exactly the same thing with 3 different prefix processors :
-
root refers to the
RootPanel
itself. The correspondingNodeObjectFinder
is registered by default inGwtCsvTest
-
app refers to the custom
EntryPoint
instance initialized though theCsvSampleTest.initApp()
method. ItsNodeObjectFinder
is registered in the setup phase of the test. -
rpcSampleView refers to the
SampleView
instance added to theRootPanel
inCsvSample.onModuleLoad()
. ItsNodeObjectFinder
is registered in the setup phase of the test.
The 4th assertion illustrates every text assertion fixtures can be used to validate HasText.getText()
. You don't have to explicitly add the /text
suffix for those.
By the way, this scenario could have been written simply with the GWT API just like we did to write basic unit tests. But for more complex UI, java based scenario could quickly become a lot more verbose (by using reflection tools explicitly, etc).
As explained here, XPath based synthax to retrieve objects should be used sparingly since it strongly couple your test to the implementation detail of your UI component. Changing the name of a component or adding a new component would break your introspection path and turn your test red.
Accessing your widgets by HTML ids would be safer and make your scenario simpler to read. Let's see how to modify the previous sample to rely on HTML ids.
Like explained in the here, you can easily setup debug ids to widgets by using UIObject.ensureDebugId() from GWT.
Just modify the view class to add relevant debug ids :
public class RpcSampleView extends Composite {
...
public RpcSampleView() {
initWidget(uiBinder.createAndBindUi(this));
label.setVisible(false);
button.setEnabled(false);
// add a debug id
ensureDebugId("rpcSampleView");
}
@Override
protected void onEnsureDebugId(String baseID) {
button.ensureDebugId(baseID + "-button");
label.ensureDebugId(baseID + "-label");
textBox.ensureDebugId(baseID + "-textBox");
}
...
}
And turn on debug ids feature in your test class :
@GwtModule("com.googlecode.gwt.test.sample.CsvSample")
@CsvDirectory(value = "functional-tests", extension = ".csv")
public class CsvSampleTest extends GwtCsvTest {
...
@Override
public boolean ensureDebugId() {
// return true to turn debug ids on in gwt-test-utils
return true;
}
}
Now we can rewrite the entire scenario to only use debug ids for accessing widgets :
A complete scenario based on widget's IDs to validate all RpcSampleView behaviors
start
** starts the CsvSample EntryPoint
initApp
** pre-assertions
isNotVisible;gwt-debug-rpcSampleView-label
assertText;*empty*;gwt-debug-rpcSampleView-textBox
isNotEnabled;gwt-debug-rpcSampleView-button
** action 1
fillTextBox;123;gwt-debug-rpcSampleView-textBox
click;gwt-debug-rpcSampleView-button
** assertions 1
isVisible;gwt-debug-rpcSampleView-label
assertText;Server error: Name must be at least 4 characters long;gwt-debug-rpcSampleView-label
** action 2
fillTextBox;test CSV;gwt-debug-rpcSampleView-textBox
click;gwt-debug-rpcSampleView-button
** assertions 2
isVisible;gwt-debug-rpcSampleView-label
assertText;Hello, test CSV!<br><br>I am running mocked-server-info.<br><br>It looks like you are using:<br>mocked-user-agent;gwt-debug-rpcSampleView-label
Sometimes, several of your integration test senarios could be quite redundant. gwt-test-utils provides CSV macros to group some instructions to be executed together.
For example, it might be usefull to group all the pre-assertions in a macro not to have to copy paste in every scenario which would make assertions on RpcSampleView
.
Create a new macros-sample.csv file in a 'functional-tests/macros' directory :
macro;InitialisationMacro
** starts the CsvSample EntryPoint
initApp
** pre-assertions
isNotVisible;gwt-debug-rpcSampleView-label
assertText;*empty*;gwt-debug-rpcSampleView-textBox
isNotEnabled;gwt-debug-rpcSampleView-button
endmacro
- The first line of a macro must be macro;name with name the name of the macro.
- The last line must ends the macro.
Than, just use it in your CSV test file :
start
** initialize the application with pre-assertions
runmacro;InitialisationMacro
** action 1
fillTextBox;123;gwt-debug-rpcSampleView-textBox
...
```
Finally, there is one thing left to do : **macros-sample.csv** must be declared as a macro file to parsed. It's done with the @CsvMacros annotation on your test class :
```java
@GwtModule("com.googlecode.gwt.test.sample.CsvSample")
@CsvDirectory(value = "functional-tests", extension = ".csv")
@CsvMacros("functional-tests/macros")
public class CsvSampleTest extends GwtCsvTest {
...
}
```
In this case, CsvSampleTest will load as a CSV macro any file in the project folder "functional-tests/macros".
## Eclipse CSV editor plugin
Writing scenarios would not offer much avantages compared to standard Java test if the IDE doesn't provide any help.
Fortunately, if you're using Eclipse, a [plugin](http://code.google.com/p/gwt-test-util-eclipse-plugin/) is available to offer syntax highlighting and fixture completion in your CSV file ! You definitely shoud give it a try ;-)