Tuesday, May 26, 2009

Test different language locale and browser type with Grails WebTest plugin.


Grails
WebTest plugin allows you to integrate Canoo WebTest into your Grails project and create tests for automatic testing. In this post we take a look at possibility to run your tests with simulation of different locales and browser types. You can use described approach for other WebTest configuration parameters.

For examples I have used grails-1.1.1, Webtest plugin 1.1.4.2 and WebTest 3.0.

Create test suite

Make sure the WebTest plugin is installed and available in your project, create some test:

grails install-plugin webtest
grails create-webtest

These commands will install WebTest plugin, make WebTest directory within your project, create TestSuite class and Test class for your domain class.
In example project (download below) my domain class is called Car, so calling grails create-webtest Car creates TestSuite.groovy and CarTest.groovy files in webtest/tests directory. The test class already contains generated tests for your domain class. You can use following command to run them:
grails run-webtest

When inspecting test results, you can see tests are run with your default language locale.

Implement static method that runs before each test.

The browser type or language locale in WebTest is set in config (configuration option browser) and header (parameter Accept-Language) elements. Before each test is run, we will explicitly set these parameters to make sure the test is run with appropriate locale and browser emulation. For this we need a way to call a (static) method before each test is run.

Let us put this static method into TestSuite class and call it globalSetUp:

public static lang = 'en'
public static browser = 'FF3'
public static Properties i18nProperties = new Properties();

def static globalSetUp(def ant) {
ant.config(summary: true, browser: browser) {
header name: 'Accept-Language', value: lang
}
}

You can see the globalSetUp method takes WebTest ant builder as a parameter and prepares config and header elements before test is created and run. It sets browser type and locale according to the values in lang and browser static variables. The static variable i18nProperties will be discussed later.

Each test class inherits WebTest class. In case setUp method exists in the test class, it is called before any test method in test class is run (see WebTest.webtestMethod in WebTest.groovy).

We have two options to call our static method before any test from test class is invoked:

  • add setUp method to each test class, which in turns calls our static method
  • make new class derived from WebTest class and implement setUp method in this class calling globalSetUp; change each test class in a way that it is inherited from this new class instead of WebTest class

(Note:In one project I have encountered path problems with second option - new class couldn not be found - but in other project there was no problem; do not know why yet.)

Let's choose first option and add to each test class following setUp method:

def setUp() {
TestSuite.globalSetUp(ant)
}

Run suite with different browser emulation

As described in config WebTest (version 3.0) can emulate following browsers:

  • Firefox3 (FF3)
  • Firefox2 (FF2)
  • InternetExplorer6 (IE6)
  • InternetExplorer7 (IE7)

To set browser emulation, change value of static variable browser:

e.g.:

public static browser = 'IE6'


and run tests:


grails run-webtest

In output you should see text:

Surfing with browser IE6

Run suite with different locales.

To run TestSuite with different locale, change value of lang static variable.
E.g.:

public static lang = 'de'

and run tests:

grails run-webtest

In output you should see text:

Configured header "Accept-Language": de


The web pages in your text should reflect selected locale.

verifyText for given locale

verifyText command makes sure there is a given text on the web page. In different locale the text will be different and we want to make sure our test suite takes this into consideration.

The text in browser is (usually) rendered with g:message tag to resolve text from message_.properties file. In example project the the Car list view has header "CAR LIST" in English version and "AUTO-LISTE" in German version. This is achieved by line in views/car/list.gsp file. This line gets the text from corresponding i18n properties file.

If we want to test this text, we can add following line to CarTest.groovy:

verifyText 'CAR LIST'

This line passes with English locale, but fails with German locale. To fix it, we will read message properties for given language into
i18nProperties static variable (introduced earlier) before the tests are run.

In TestSuite.groovy file find


for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
and change it to:
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}

Modify previous verifyText (so it uses text from properties file):

verifyText TestSuite.i18nProperties.getProperty("webtest-multi.carlist")

Now, the test will pass for both, English and German locale.

Multiple locales and browser emulations in one test suite

If you want to change locale or browser type, you have to change static variable lang or browser before each run. Another option is to modify test suite method to iterate through browsers and/or locales and run test suite several times.
e.g.:

TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
['IE6', 'IE7', 'FF2', 'FF3'].each { br ->
browser = br
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}

or:

['de', 'en'].each { lo ->
lang = lo
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}


or even:

['de', 'en'].each { lo ->
lang = lo
TestSuite.i18nProperties.load(new FileInputStream("grails-app/i18n/messages_${lang}.properties"));
['IE6', 'IE7', 'FF2', 'FF3'].each { br ->
browser = br
for (file in scanner) {
def test = getClass().classLoader.parseClass(file).newInstance()
test.ant = ant
test.suite()
}
}
}

At first glance, this approach seems obvious. Unfortunately it is only useful for test suites which do not change the context (database) after they pass or fail or for test suites that do not depend on this context (for example no dependency on number of rows in the table). If you do not have such tests, you have to reset the context after each run of suite (after call of test.suite() method) in some way. Currently I do not know any proper way how to do it.

Conclusion

WebTest is testing framework which is well integrated into grails. In this post you have seen how to modify attributes of config and header elements for your tests and how to use this modification to emulate different browser types and change language locales. This is can be even more useful, if you can iterate trough the array of possible values directly in TestSuite script. Unfortunately this is only possible for tests that do not change database context.

External links: