Testing REST services with REST Assured

For those of you wanting to add the possibility to validate RESTful web services to your test automation framework, REST Assured can be a very useful way to do just that. In this post, I want to show you how to perform some basic tests on both JSON and XML-based REST services.

Installing and configuring REST Assured is easy, just download the latest version from the website and add the relevant .jar files to your testing project and you’re good to go.

Testing a REST service that returns XML
First, let’s try validating a REST service that returns a response in XML format. As an example, I use the ParaBank REST service to get the customer details for a customer with ID 12212 (click here to invoke the service and see the response in your browser). For this response, I want to check that the returned customer ID is equal to 12212, that the first name is equal to John, and that the last name is equal to Doe. The following REST Assured-test does exactly that. Note that the test consists of just a single statement, but I’ve added some line breaks to make it more readable.

package com.ontestautomation.restassured.test;

import static com.jayway.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class RestServiceTest {
	
	public static void main (String args[]) {
		
		get("http://parabank.parasoft.com/parabank/services/bank/customers/12212/").
		then().
			assertThat().body("customer.id", equalTo("12212")).
		and().
			assertThat().body("customer.firstName", equalTo("John")).
		and().
			assertThat().body("customer.lastName", equalTo("Doe"));		
	}
}

Pretty straightforward, right? All you need to do is invoke the service using get() and then perform the required checks using assertThat() and the equalTo() matcher (which is part of Hamcrest by the way, not of REST Assured). Identifying the response elements to be checked is done using a dot notation. For example, customer.id identifies the id element that is a child of the customer element, which is the root element of the response message.

When we run this test, we see the results displayed neatly in our IDE:
REST Assured test resultsOne of the assertions fails, since the actual last name for customer 12212 is Smith, not Doe.

Testing a REST service that returns JSON
REST Assured can be used just as easily to perform checks on REST services that return JSON instead of XML. In the next example, I use a REST service that takes a text string and returns the md5 checksum for that string, together with the original string.
REST service with JSON responseThe following code example checks both the original string value and the md5 checksum value for this REST service:

package com.ontestautomation.restassured.test;

import static com.jayway.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class RestServiceTestGherkin {
	
	public static void main(String args[]) {
		
		given().
			parameters("text", "test").
		when().
			get("http://md5.jsontest.com").
		then().
			body("md5",equalTo("098f6bcd4621d373cade4e832627b4f6")).
		and().
			body("original", equalTo("incorrect"));
	}
}

As you can see, checking values from JSON responses is just as straightforward as is the case for XML responses. Two additional things do have changed in this example though:

  • As this web service call takes parameters – in this case a single parameter containing the string for which the md5 value is to be calculated – we need to define the value of this parameter before calling the service. This is done using the parameters() method, which takes parameter key-value pairs as its argument. Alternatively, we could have omitted the parameters call and just perform a get(“http://md5.jsontest.com?text=test”) instead, but this is both more elegant and more maintainable.
  • Also, as you can see from this example, in REST Assured, you can specify your tests in a BDD format using the Given-When-Then construction that is also used (for example) in Cucumber.

When we run this test, again, the test results show up nicely in our IDE:
Test results for JSON REST service testI’ve added another defect on purpose to show how they are displayed: the value of the original element in the response should of course be test instead of incorrect.

Combining REST Assured and TestNG
Finally, we can also combine REST Assured with TestNG to create an even more powerful REST service testing framework. In that way, you can for example use the reporting capabilities of TestNG for your REST service tests, as well as integrate REST Assured tests into an existing test framework. Doing so is as easy as replacing the default Hamcrest assertions with TestNG assertions and performing the required checks:

package com.ontestautomation.restassured.test;

import static com.jayway.restassured.RestAssured.*;

import org.testng.Assert;
import org.testng.annotations.Test;

import com.jayway.restassured.path.xml.XmlPath;

public class RestServiceTestNG {
	
	@Test
	public void testng() {
		
		// Get the response XML from the REST service and store it as a String
		String xml = get("http://parabank.parasoft.com/parabank/services/bank/customers/12212/").andReturn().asString();
		
		// Retrieve the values to be checked from the XML as a String
		XmlPath xmlPath = new XmlPath(xml).setRoot("customer");
		String customerId = xmlPath.getString("id");
		String firstName = xmlPath.getString("firstName");
		String lastName = xmlPath.getString("lastName");
		
		// Perform the required checks
		Assert.assertEquals(customerId, "12212");
		Assert.assertEquals(firstName, "John");
		Assert.assertEquals(lastName, "Doe");
	}
}

When using TestNG, we first store the response of the web service as a String, after which we can check whatever we want to check. It does make the test code a little longer, and in my personal opinion a little less elegant. On the upside, we can now seamlessly integrate our REST Assured-tests in a larger TestNG-based test framework and profit from the automatic report generation that TestNG provides.

Running this test results in the following output in our IDE:
Test results for TestNG-based testsand the following HTML report:
HTML results for the TestNG-based REST Assured testsAdditional features
Apart from the options shown above, REST Assured provides some other features that can come in very handy when testing REST-based web services. For instance, you can also test services that require authentication in order to be invoked. For basic authentication, you can simply add the credentials to your test statement as follows:

given().auth().basic("username", "password").when().

It is also possible to invoke services that require OAuth authorization (this requires the help of the Scribe library):

given().auth().oauth(..).when().

If you want to explicitly validate that a web service returns the correct content type (JSON, in this case), you can do this:

get("/path/to/service").then().assertThat().contentType(ContentType.JSON)

For a complete overview of all REST Assured features, you can refer to the online documentation.

Happy REST testing!

Using the Page Object Model pattern in Selenium + TestNG tests

After having introduced the Selenium + TestNG combination in my previous post, I would like to show you how to apply the Page Object Model, an often used method for improving maintainability of Selenium tests, to this setup. To do so, we need to accomplish the following steps:

  • Create Page Objects representing pages of a web application that we want to test
  • Create methods for these Page Objects that represent actions we want to perform within the pages that they represent
  • Create tests that perform these actions in the required order and performs checks that make up the test scenario
  • Run the tests as TestNG tests and inspect the results

Creating Page Objects for our test application
For this purpose, again I use the ParaBank demo application that can be found here. I’ve narrowed the scope of my tests down to just three of the pages in this application: the login page, the home page (where you end up after a successful login) and an error page (where you land after a failed login attempt). As an example, this is the code for the login page:

package com.ontestautomation.seleniumtestngpom.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class LoginPage {
	
	private WebDriver driver;
	
	public LoginPage(WebDriver driver) {
		
		this.driver = driver;
		
		if(!driver.getTitle().equals("ParaBank | Welcome | Online Banking")) {
			driver.get("http://parabank.parasoft.com");
		}		
	}
	
	public ErrorPage incorrectLogin(String username, String password) {
		
		driver.findElement(By.name("username")).sendKeys(username);
		driver.findElement(By.name("password")).sendKeys(password);
		driver.findElement(By.xpath("//input[@value='Log In']")).click();
		return new ErrorPage(driver);
	}
	
	public HomePage correctLogin(String username, String password) {
		
		driver.findElement(By.name("username")).sendKeys(username);
		driver.findElement(By.name("password")).sendKeys(password);
		driver.findElement(By.xpath("//input[@value='Log In']")).click();
		return new HomePage(driver);
	}
}

It contains a constructor that returns a new instance of the LoginPage object as well as two methods that we can use in our tests: incorrectLogin, which sends us to the error page and correctLogin, which sends us to the home page. Likewise, I’ve constructed Page Objects for these two pages as well. A link to those implementations can be found at the end of this post.

Note that this code snippet isn’t optimized for maintainability – I used direct references to element properties rather than some sort of element-level abstraction, such as an Object Repository.

Creating methods that perform actions on the Page Objects
You’ve seen these for the login page in the code sample above. I’ve included similar methods for the other two pages. A good example can be seen in the implementation of the error page Page Object:

package com.ontestautomation.seleniumtestngpom.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class ErrorPage {
	
	private WebDriver driver;
	
	public ErrorPage(WebDriver driver) {
		
		this.driver = driver;
	}
	
	public String getErrorText() {
		
		return driver.findElement(By.className("error")).getText();
	}
}

By implementing a getErrorText method to retrieve the error message that is displayed on the error page, we can call this method in our actual test script. It is considered good practice to separate the implementation of your Page Objects from the actual assertions that are performed in your test script (separation of responsibilities). If you need to perform additional checks, just add a method that returns the actual value displayed on the screen to the associated page object and add assertions to the scripts where this check needs to be performed.

Create tests that perform the required actions and execute the required checks
Now that we have created both the page objects and the methods that we want to use for the checks in our test scripts, it’s time to create these test scripts. This is again pretty straightforward, as this example shows (imports removed for brevity):

package com.ontestautomation.seleniumtestngpom.tests;

public class TestNGPOM {
	
	WebDriver driver;
	
	@BeforeSuite
	public void setUp() {
		
		driver = new FirefoxDriver();
		driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
	}
	
	@Parameters({"username","incorrectpassword"})
	@Test(description="Performs an unsuccessful login and checks the resulting error message")
	public void testLoginNOK(String username, String incorrectpassword) {
		
		LoginPage lp = new LoginPage(driver);
		ErrorPage ep = lp.incorrectLogin(username, incorrectpassword);
		Assert.assertEquals(ep.getErrorText(), "The username and password could not be verified.");
	}
	
	@AfterSuite
	public void tearDown() {
		
		driver.quit();
	}
}

Note the use of the page objects and the check being performed using methods in these page object implementations – in this case the getErrorText method in the error page page object.

As we have designed our tests as Selenium + TestNG tests, we also need to define a testng.xml file that defines which tests we need to run and what parameter values the parameterized testLoginOK test takes. Again, see my previous post for more details.

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
 
<suite name="My first TestNG test suite" verbose="1" >
  <parameter name="username" value="john"/>
  <parameter name="password" value="demo"/>
  <test name="Login tests">
    <packages>
      <package name="com.ontestautomation.seleniumtestngpom.tests" />
   </packages>
 </test>
</suite>

Run the tests as TestNG tests and inspect the results
Finally, we can run our tests again by right-clicking on the testng.xml file in the Package Explorer and selecting ‘Run As > TestNG Suite’. After test execution has finished, the test results will appear in the ‘Results of running suite’ tab in Eclipse. Again, please note that using meaningful names for tests and test suites in the testng.xml file make these results much easier to read and interpret.

TestNG test results in Eclipse

An extended HTML report can be found in the test-output subdirectory of your project:

TestNG HTML test results

The Eclipse project I have used for the example described in this post, including a sample HTML report as generated by TestNG, can be downloaded here.

Up and running with: TestNG

This is the fifth article in our series on new, popular or otherwise interesting tools used in test automation. You can read all posts within this series by clicking here.

What is TestNG?
From the TestNG.org website: TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use, such as annotations and support for data driven testing.

Where can I get TestNG?
TestNG can be downloaded from this site. For Eclipse users, it is highly recommended to install the TestNG plugin for Eclipse for maximum ease of use. IDEA IntelliJ supports TestNG natively as of version 7.

How do I install and configure TestNG?
Since TestNG is supported natively from IntelliJ 7 onwards, there’s no need for additional configuration for IntelliJ users. When you install the TestNG for Eclipse plugin as described, you’re set to create your first TestNG test as well, as can be read here. In other situations, you can download the .jar files from here as well.

Creating a first TestNG test
As I have done in the past, I’ll (ab)use the ParaBank demo application at the Parasoft website for our first TestNG test. I’ll use Selenium WebDriver to perform the test steps, and will use TestNG to perform the checks that we want to do and the reporting. Let’s say we want to verify that we can login successfully to the ParaBank application, given the right credentials. A Selenium + TestNG test that performs this test looks like this (import statements removed for brevity):

public class ParabankTestNG {
	
	WebDriver driver;
	
	@BeforeSuite
	public void setUp() {
		
		driver = new FirefoxDriver();
		driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
	}
	
	@Test(description="Tests a successful login")
	public void testLoginOK() {
		
		driver.get("http://parabank.parasoft.com");
		driver.findElement(By.name("username")).sendKeys("john");
		driver.findElement(By.name("password")).sendKeys("demo");
		driver.findElement(By.xpath("//input[@value='Log In']")).click();
		Assert.assertEquals("ParaBank | Accounts Overview",driver.getTitle());
		driver.findElement(By.partialLinkText("Log Out")).click();
	}
	
	@AfterSuite
	public void tearDown() {
		
		driver.quit();
	}
}

Note that this test looks almost identical to a Selenium + JUnit test. The only difference is the use of the @BeforeSuite and @AfterSuite annotations for test setup and teardown, where we would use @Before and @After in JUnit. TestNG uses a large variety of annotations that can be used to enhance your tests and test suites.

Running the test
Again, as I’m an Eclipse user, I’ll show you how to execute your tests in Eclipse only. Please refer to the TestNG website for instructions for other IDEs.

For those that installed the TestNG plugin for Eclipse, there are two simple ways to start a TestNG test. First, we can simply right-click our test .java file in the Package Explorer and select ‘Run As > TestNG Test’. This is perfectly suitable when you have a single class containing all of your tests.

However, for larger projects, this will typically not be the case. For those situations, we can create an XML file called testng.xml that contains instructions on which tests to run and how they should be run. You can find instructions on the use of testng.xml files here. As an example, we can run all TestNG tests in a specific package using the following instructions:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
 
<suite name="My first TestNG test suite" verbose="1" >
  <test name="Login tests"   >
    <packages>
      <package name="com.ontestautomation.selenium.testng" />
   </packages>
 </test>
</suite>

The verbose attribute specifies the verbosity of information logged to the console, where 1 is low and 10 is high.

Running a test using the testng.xml file can be done just as easily by right-clicking on it in the Package Explorer and selecting ‘Run As > TestNG Suite’. Test results can be reviewed in the ‘Results of running suite’ tab in Eclipse. Note that using meaningful names for tests and test suites in the testng.xml file make these results much easier to read and interpret:

TestNG test results in Eclipse

Using the testng.xml file also makes it easy to specify exactly which tests are run in a specific test suite, and also in which order they are executed. By default, the order in which the tests appear in the testng.xml file defines the order in which the tests are run.

Useful features
one very useful feature of TestNG is the ability to easily parameterize tests from testng.xml. For example, if we want to parameterize the input values from the login test above, we first redefine the method in the test class to take parameters:

@Parameters({"username","password"})
@Test(description="Tests a successful login")
public void testLoginOK(String username, String password) {
		
	driver.get("http://parabank.parasoft.com");
	driver.findElement(By.name("username")).sendKeys(username);
	driver.findElement(By.name("password")).sendKeys(password);
	driver.findElement(By.xpath("//input[@value='Log In']")).click();
	Assert.assertEquals("ParaBank | Accounts Overview",driver.getTitle());
	driver.findElement(By.partialLinkText("Log Out")).click();
}

Note the use of the @Parameter annotation to link the arguments of our test method to the parameters you define in the testng.xml file:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
 
<suite name="My first TestNG test suite" verbose="1" >
  <parameter name="username" value="john"/>
  <parameter name="password" value="demo"/>
  <test name="Login tests"   >
    <packages>
      <package name="com.ontestautomation.selenium.testng" />
   </packages>
 </test>
</suite>

Another useful option of TestNG is the fact that it automatically generates readable HTML reports containing the test results. By default, these are created in a test-output directory relative to your project location. The HTML report generated from the test above, for example, looks as follows:
TestNG HTML test report
You can further personalize and adjust the reports as described here.

Further reading
An Eclipse project including the TestNG test I’ve demonstrated above and the reports that have been generated can be downloaded here.

Happy TestNG testing! Since I’ve got to know TestNG in the past couple of weeks, I’ve discovered quite a few interesting possibilities that I want to share with you in future posts, so stay tuned.