Creating data driven API tests with REST Assured and TestNG

As you’ve probably read in previous posts on this site, I am a big fan of the REST Assured library for writing tests for RESTful web services. In this post, I will demonstrate how to create even more powerful tests with REST Assured by applying the data driven principle, with some help from TestNG.

Combining REST Assured and TestNG
REST Assured itself is a Domain Specific Language (DSL) for writing tests for RESTful web services and does not offer a mechanism for writing data driven tests (i.e., the ability to write a single test that can be executed multiple times with different sets of input and validation parameters). However, REST Assured tests are often combined with JUnit or TestNG, and the latter offers an easy to use mechanism to create data driven tests through the use of the DataProvider mechanism.

Creating a TestNG DataProvider
A TestNG DataProvider is a method that returns an object containing test data that can then be fed to the actual tests (REST Assured tests in this case). This data can be hardcoded, but it can also be read from a database or a JSON specification, for example. It’s simply a matter of implementing the DataProvider in the desired way. The following example DataProvider creates a test data object (labeled md5hashes) that contains some example text strings and their md5 hash:

@DataProvider(name = "md5hashes")
public String[][] createMD5TestData() {
		
	return new String[][] {
			{"testcaseOne", "4ff1c9b1d1f23c6def53f957b1ed827f"},
			{"testcaseTwo", "39738347fb533d798aca9ae0f56ca126"},
			{"testcaseThree", "db6b151bb4bde46fddb361043bc3e2d9"}
	};
}

Using the DataProvider in REST Assured tests (with query parameters)
Using the data specified in the DataProvider in REST Assured tests is fairly straightforward. In the example below, we’re using it in combination with a RESTful web service that uses a single query parameter:

@Test(dataProvider = "md5hashes")
public void md5JsonTest(String originalText, String md5Hash) {
		
	given().
		parameters("text", originalText).
	when().
		get("http://md5.jsontest.com").
	then().
		assertThat().
		body("md5", equalTo(md5Hash));
}

Notice how the data in the DataProvider can be used for specification of expected results just as easy as for specifying input parameters. When we run this test, we can see in the output that the test is run three times, once for every set of input and validation parameters:

Console output for our data driven REST Assured test

Using the DataProvider in REST Assured tests (with query parameters)
The other parameter approach commonly used with RESTful web services is the use of path parameters. Let’s create another DataProvider, this one specifying Formula 1 circuits and the country they are situated in:

@DataProvider(name = "circuitLocations")
public String[][] createCircuitTestData() {
		
	return new String[][] {
			{"adelaide","Australia"},
			{"detroit","USA"},
			{"george","South Africa"}
	};
}

The Ergast Developer API provides motor racing data through a public API (check it out, it’s really cool). Here’s how to parameterize the call to get specific circuit data and check the country for each circuit with REST Assured, again using our DataProvider:

@Test(dataProvider = "circuitLocations")
public void circuitLocationTest(String circuitId, String location) {
		
	given().
		pathParameters("circuitId",circuitId).
	when().
		get("http://ergast.com/api/f1/circuits/{circuitId}.json").
	then().
		assertThat().
		body("MRData.CircuitTable.Circuits[0].Location.country",equalTo(location));
}

The only real difference is in how to set up path parameter use in REST Assured (see also here), otherwise making your tests data driven is just as easy as with query parameters.

When we run this second test, we can see again that it’s run three times, once for every circuit specified:

Console output for our data driven REST Assured test (with path parameters_

A Maven Eclipse project containing the code demonstrated in this blog post can be downloaded here.

Data driven JavaScript tests using Jasmine

In my previous post I introduced Jasmine as a tool for testing JavaScript code. Now that I’ve had some more time to play around with it, combining it with Protractor for user interface-driven tests for one of my client’s websites, I’ve noticed that one feature was sorely missing: built-in support for data driven testing. All BDD tools (or tools that say they support BDD) that I have worked with so far, such as Cucumber, SpecFlow and JGiven, support the creation of data driven scenarios. However, Jasmine doesn’t, at least not as far as I have seen in the limited time I have been using it so far.

As I think data driven testing is key when creating maintainable and reusable scenarios, I fired up Google to check whether someone had already found a solution. From what I’ve read, there are (at least) two different ways to do data-driven testing with Jasmine. Let’s have a look at both.

Specifying your test data in an array variable
I found the first approach in the comments of this StackOverflow post. The comment submitted by peterhendrick suggests the use of a JSON file containing the test data. I slightly simplified this to use a simple array of key-value pairs and have the Jasmine spec loop through the array. You can simply populate this array any way you like, for example from an external .json file as suggested, but in any other way as well. In this example, I hard-coded it in the spec itself:

describe("Calculator (data driven tests with test data specified in array)", function() {
  var calc;
  var testdata = [{"x":1,"y":1,"sum":2},{"x":2,"y":3,"sum":5},{"x":4,"y":2,"sum":6}];
  
  beforeEach(function() {
    calc = new Calculator();
  });

	for(var i in testdata) {
		it("should be able to add two numbers, using (" + testdata[i].x + "," + testdata[i].y + "," + testdata[i].sum + ")", function() {
			
			expect(calc.add(testdata[i].x, testdata[i].y)).toEqual(testdata[i].sum);
		});	
	};
});

I particularly like this example because it enables you to add the actual values used to the test output. You can see this when you run your spec:

Test results from driving test data through an array

Data driven testing with a custom using function
This blog post from JP Castro presents an alternative way to do data driven testing in Jasmine. His solution seemed easy enough for me to apply directly to my own specs. Let’s see what it looks like.

The basic idea behind his solution is to wrap each it block in a spec with a using block that passes the parameter values to the it block:

describe("Calculator (data driven tests with using() function)", function() {
  var calc;
  
  beforeEach(function() {
    calc = new Calculator();
  });

  using('parameters',[[1,1,2],[2,3,5],[4,2,6]], function(parameters){
	it("should be able to add two numbers, using (" + parameters[0] + "," + parameters[1] + "," + parameters[2] + ")", function() {
		expect(calc.add(parameters[0],parameters[1])).toEqual(parameters[2]);
	});
  });
});

The using function he presents is implemented as follows:

function using(name, values, func) {
	for(var i = 0, count = values.length; i < count; i++) {
		if(Object.prototype.toString.call(values[i]) !== '[Object Array]') {
			values[i] = [values[i]];
		}
		func.apply(this,values[i]);
	}
}

When we run the spec above, the output looks like this:

Test results from driving test data through a using function

So, now we have two simple (and admittedly similar) workarounds for what I think is still a fundamental shortcoming in Jasmine. The code demonstrated in this blog post can be downloaded here.

Defining test steps and object properties in an Excel Data Source for Selenium

Recently, one of the readers of this blog sent me an email asking whether it was possible to define not only input and validation parameters in an external data source (such as an Excel file), but also the test steps to be taken and the object properties on which these steps need to be performed by Selenium WebDriver. As it happens, I have been playing around with this idea for a while in the past, but until now I’ve never gotten round to writing a blog post on it.

The Excel Data Source
As an example, we are going to perform a search query on Google and validate the number of results we get from this search query. This scripts consists of the following steps:

  1. Open a new browser instance
  2. Navigate to the Google homepage
  3. Type the search query in the text box
  4. Click on the search button
  5. Validate the contents of the web element displaying the number of search results against our expected value
  6. Close the browser instance

For each step, we are going to define (if applicable):

  • A keyword identifying the action to be taken
  • A parameter value that is used in the action (note that for example a type action takes a parameter, a click action does not)
  • The attribute type that uniquely identifies the object on which the action is performed
  • The corresponding attribute value

For our example test script, the Excel Data Source to be used could look something like this:
Excel Data Source
Reading the Data Source
Similar to the concept explained in a previous post, first we need to read the values from the Excel sheet in order to execute the test steps defined. We need to be a little extra careful here as some cells might be empty and the methods we use do not particularly like reading values from cells that do not exist.

public static void main (String args[]) {

		String action = "";
		String value = "";
		String attribute = "";
		String attrval = "";
		
		try {
			// Open the Excel file for reading
			FileInputStream fis = new FileInputStream("C:\\Tools\\testscript.xls");
			// Open it for writing too
			FileOutputStream fos = new FileOutputStream("C:\\Tools\\testscript.xls");
			// Access the required test data sheet
			HSSFWorkbook wb = new HSSFWorkbook(fis);
			HSSFSheet sheet = wb.getSheet("steps");
			// Loop through all rows in the sheet
			// Start at row 1 as row 0 is our header row
			for(int count = 1;count<=sheet.getLastRowNum();count++){
				HSSFRow row = sheet.getRow(count);
				System.out.println("Running test step " + row.getCell(0).toString());

				// Run the test step for the current test data row
				if(!(row.getCell(1) == null || row.getCell(1).equals(Cell.CELL_TYPE_BLANK))) {
					action = row.getCell(1).toString();
				} else {
					action = "";
				}

				if(!(row.getCell(2) == null || row.getCell(2).equals(Cell.CELL_TYPE_BLANK))) {
					value = row.getCell(2).toString();
				} else {
					value = "";
				}

				if(!(row.getCell(3) == null || row.getCell(3).equals(Cell.CELL_TYPE_BLANK))) {
					attribute = row.getCell(3).toString();
				} else {
					attribute = "";
				}

				if(!(row.getCell(4) == null || row.getCell(4).equals(Cell.CELL_TYPE_BLANK))) {
					attrval = row.getCell(4).toString();
				} else {
					attrval = "";
				}

				System.out.println("Test action: " + action);
				System.out.println("Parameter value: " + value);
				System.out.println("Attribute: " + attribute);
				System.out.println("Attribute value: " + attrval);
				
				String result = runTestStep(action,value,attribute,attrval);
				
				// Write the result back to the Excel sheet
				row.createCell(5).setCellValue(result);
				
			}
			
			// Save the Excel sheet and close the file streams
			wb.write(fos);
			fis.close();
			fos.close();
			
		} catch (Exception e) {
			System.out.println(e.toString());
		}
}

We do not read the value from the Result column, but rather we are going to execute the test steps using the values from the other columns, determine the result and write this back to the Excel sheet. In this way, we have a rudimentary logging function built in directly into our framework. Neat, right?

Defining and executing test steps
Next, we need to implement the generic runTestStep method we use to execute the test steps we have defined in our Data Source. This can be done pretty straightforward by looking at the current Action keyword and then executing the necessary steps for that keyword.

public static String runTestStep(String action, String value, String attribute, String attrval) throws Exception {

		switch(action.toLowerCase()) {
		case "openbrowser":
			switch(value.toLowerCase()) {
			case "firefox":
				driver = new FirefoxDriver();
				driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
				return "OK";
			case "htmlunit":
				driver = new HtmlUnitDriver();
				driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
				return "OK";
			default:
				return "NOK";
			}
		case "navigate":
			driver.get(value);
			return "OK";
		case "type":
			try {
				WebElement element = findMyElement(attribute,attrval);
				element.sendKeys(value);
				return "OK";
			} catch (Exception e) {
				System.out.println(e.toString());
				return "NOK";
			}
		case "click":
			try {
				WebElement element = findMyElement(attribute,attrval);
				element.click();
				return "OK";
			} catch (Exception e) {
				System.out.println(e.toString());
				return "NOK";
			}
		case "validate":
			try {
				WebElement element = findMyElement(attribute,attrval);
				if (element.getText().equals(value)) {
					return "OK";
				} else {
					System.out.println("Actual value: " + element.getText() + ", expected value: " + value);
					return "NOK";
				}
			} catch (Exception e) {
				System.out.println(e.toString());
			}
		case "closebrowser":
			driver.quit();
			return "OK";
		default:
			throw new Exception("Unknown keyword " + action);
		}
}

This setup makes it very easy to add other keywords (e.g., for selecting a value from a dropdown list), both for standardized browser actions as well as for custom keywords. Every step returns a result (here either OK or NOK) to indicate whether the action has been executed successfully. This is written back to the Excel sheet in our main method, so we can see whether our test steps have been executed correctly.

Finding the required object
I’ve also used a helper method findMyElement that, given an attribute and an attribute value, returns a WebElement that corresponds to these values. Its implementation is quite rudimentary, but it should be easy for you to extend it and make it more fail safe. I’m lazy sometimes. I haven’t even implemented all types of selectors that Selenium can handle!

public static WebElement findMyElement(String attribute, String attrval) throws Exception {

		switch(attribute.toLowerCase()) {
		case "id":
			return driver.findElement(By.id(attrval));
		case "name":
			return driver.findElement(By.name(attrval));
		case "xpath":
			return driver.findElement(By.xpath(attrval));
		case "css-select":
			return driver.findElement(By.cssSelector(attrval));
		default:
			throw new Exception("Unknown selector type " + attribute);
		}
}

Now, when I run the test, my code nicely reads all rows from Excel, determines what test action needs to be performed, executes it and reports the result back to Excel. After running the test, the Excel sheet looks like this:
Excel Data Source including test results
The only test step that fails does so because it Google doesn’t always return the same number of test results in the same amount of time, obviously. Nevertheless, it illustrates the added value of writing back the result of every step to the Excel sheet. Of course, you can very easily extend this to include a useful error message and even a link to a screenshot.

The Eclipse project containing the code I’ve used to create this example can be downloaded here.