Skip to content

Writing CSV scenarios

Gael Lazzari edited this page Sep 10, 2012 · 14 revisions

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.

A first scenario

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 framework initApp is a fixture which can be invoked in CSV tests.

  • Finally, some custom NodeObjectFinder have been registered to the GwtFinder helper object described here. Those finders relies on the inherited CsvRunner 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 as assertNotExist, 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 corresponding NodeObjectFinder is registered by default in GwtCsvTest

  • app refers to the custom EntryPoint instance initialized though the CsvSampleTest.initApp() method. Its NodeObjectFinder is registered in the setup phase of the test.

  • rpcSampleView refers to the SampleView instance added to the RootPanel in CsvSample.onModuleLoad(). Its NodeObjectFinder 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).

IDs based scenarios

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

Using macros

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 ;-)