Skip to content

Step 12 testing it all

opensas edited this page Nov 3, 2011 · 10 revisions

Step 12 - testing it all

Purpose: in this step we will see how to build automated tests.

cd ~/devel/apps
git clone git://github.com/opensas/play-demo.git
cd play-demo
git checkout origin/12-testing_it_all

Testing your app

Play framework comes with an integrated test suite, and it support three types of tests: unit tests, functional tests and aceptance tests.

Before we begin we'll make sure that our tests run with a specified configuration. Edit application.conf and create the items with the configuration for test mode preffixed with %test., like this:

conf/application.conf

# Testing. Set up a custom configuration for test mode
# ~~~~~
#%test.module.cobertura=${play.path}/modules/cobertura
%test.application.mode=dev
%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0
%test.jpa.ddl=create
%test.mail.smtp=mock

%test.application.langs=en

Here we specified that the language to use in test mode will be english.

Creating testing data

Before running your test, you have to make sure you count with a known set of data. Create the following file:

test: data-test.yml

EventType(presentation):
   name: presentation

EventType(workshop):
   name: workshop

Event(first):
   name: Drools and jBPM5
   type: presentation
   place: 64 rue Taitbout, PARIS
   date: 06-17-2011 9:00:00

Event(second):
   name: Search Engines
   type: presentation
   place: One Brattle Square, Cambridge
   date: 06-23-2011 18:30:00

Event(Third):
   name: Scale the Everest with Scala
   type: presentation
   place: One Brattle Square, Cambridge
   date: 04-08-2011 20:00:00

Event(Fourth):
   name: Play! framework, a java web framework for non-purists
   type: workshop
   place: 64 rue Taitbout, PARIS
   date: 08-25-2012 20:00:00

Unit tests

Now we'll create a unit test to test our helper function dateDiff.

Rename test.BasicTest.java to DateDiffTest.java and enter the following code:

test:DateDiffTest.java

import java.util.Date;

import lib.utils.DateHelper;

import org.joda.time.DateTime;
import org.junit.Test;

import play.test.UnitTest;

public class DateDiffTest extends UnitTest {

    @Test
    public void dateDiffTest() {

    	assertEquals("1 second", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 0, 0).toDate(), 
    			new DateTime(2011, 1, 1, 10, 30, 1, 0).toDate()
    	));
    	
    	assertEquals("10 seconds", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 0, 0).toDate(), 
    			new DateTime(2011, 1, 1, 10, 30, 10, 0).toDate()
    	));

    	assertEquals("5 minutes, 30 seconds", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 0, 0).toDate(), 
    			new DateTime(2011, 1, 1, 10, 35, 30, 0).toDate()
    	));

    	assertEquals("-5 minutes, -30 seconds", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 35, 30, 0).toDate(),
    			new DateTime(2011, 1, 1, 10, 30, 0, 0).toDate()
    	));

    	assertEquals("1 year, 2 months, 5 days, 3 hours, 15 minutes, 5 seconds", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 0, 0).toDate(), 
    			new DateTime(2012, 3, 6, 13, 45, 5, 0).toDate()
    	));

    	assertEquals("5 years, 5 months, 5 days, 5 hours, 5 minutes, 5 seconds", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 10, 0).toDate(), 
    			new DateTime(2016, 6, 6, 15, 35, 15, 0).toDate()
    	));

    	//singular form
    	assertEquals("1 year, 1 month, 1 day, 1 hour, 1 minute, 1 second", DateHelper.dateDiff(
    			new DateTime(2011, 1, 1, 10, 30, 10, 0).toDate(), 
    			new DateTime(2012, 2, 2, 11, 31, 11, 0).toDate()
    	));
	
    }
}

Now stop the application, and restart it in test mode. You can do it from the command line with the play test command or from the Eclipse IDE selecting Run, Run configuration, test play-demo. Select DateDiffTest and run, it should pass the test.

Functional tests

A functional test lets you test your application accesing directly to your controllers. We'll use it to test our delete action. Rename AplicationTest.java to DeleteEventTest.java and enter the following code:

test:DeleteEventTest.java

import models.Event;

import org.junit.Before;
import org.junit.Test;

import play.db.jpa.JPA;
import play.mvc.Http.Response;
import play.test.Fixtures;
import play.test.FunctionalTest;

public class DeleteEventTest extends FunctionalTest {

	@Before
	public void deleteModels() {
		Fixtures.deleteAllModels();
		Fixtures.loadModels("data-test.yml");
	}

    @Test
    public void deleteEventTest() {

    	final Long originalCount = Event.count();
    	final Event originalNextEvent = Event.find("order by date desc").first();
    	
        Response response = DELETE("/event/" + originalNextEvent.id); 
        assertStatus(302, response);
        assertHeaderEquals("Location", "/", response);
        
        assertEquals("There should be one less event", originalCount-1, Event.count());
        
        assertNotSame("The next event must have changed",
                originalNextEvent,
                Event.find("order by date desc").first()
        );

        final Event deletedEventByCondition = Event.find("id = ?", originalNextEvent.id).first();
        assertNull("The event should have been deleted", deletedEventByCondition);
    }
    
}

In this test we get the id of the next event, then we issue and http request to delete it, and after that we check that the event has effectively been removed from the database. Notice how we use fixtures to work with known predefined set of events.

Aceptance tests

Acceptance tests are written using selenium tags, and running them with an automated browser. In this case we will first develop a test that deletes all events.

Here you have the selenium comand reference. You should have a look at all the commands available.

test: EventCrud.test.html

#{fixture delete:'all', load:'data-test.yml' /}

#{selenium}

    // Open the home page, and check that no error occured
    open('/')
    assertNotTitle('Application error')

    clickAndWait('xpath=//a[text()="new event"]')

    clickAndWait('css=button[type="submit"]')

    assertTextPresent('Oops! Errors detected')
    assertTextPresent('You have to complete the event\'s name.')
    assertTextPresent('You have to select the type of the event.')
    assertTextPresent('You have to complete the event\'s place')
    assertTextPresent('You have to complete the event\'s date.')
	
    type('event.name', 'new event')
	
    select( 'event.type.id', 'label=workshop')
	
    type('event.place', 'event\'s place')
	
    # test date format
    type('event.date', '25-10-2011 18:30')
	
    clickAndWait('css=button[type="submit"]')

    assertTextPresent('Oops! Errors detected')
    assertTextNotPresent('You have to complete the event\'s name.')
    assertTextNotPresent('You have to select the type of the event.')
    assertTextNotPresent('You have to complete the event\'s place')
    assertTextNotPresent('You have to complete the event\'s date.')
    
    assertTextPresent('Incorrect value')

    # test date format
    type('event.date', '10-25-2011 18:30')

    clickAndWait('css=button[type="submit"]')

    assertTextPresent('event successfully saved!')
    assertTextPresent('new event')
	
    # edit the newly created event
    clickAndWait('xpath=//a[text()="new event"]')
	
    type('event.name', 'new event modified')
    clickAndWait('css=button[type="submit"]')
	
    assertTextPresent('event successfully saved!')
    assertTextPresent('new event modified')
        
    # delete the newly create event
    click('xpath=//a[text()="new event modified"]/../..//a/img/..')
    waitForTextNotPresent('new event modified')
    assertTextNotPresent('new event modified')
	
#{/selenium}

The next test will test the whole crud operation, that is it will create a new event, check that the validations are working, save the event, edit it, and finally delete it.

test: EventDelete.test.html

#{fixture delete:'all', load:'data-test.yml' /}

#{selenium}

    // Open the home page, and check that no error occured
    open('/')
    assertNotTitle('Application error')

    // Delete Play! framework event
    assertTextPresent('Play! framework, a java web framework for non-purists')
    click('xpath=//a[text()="Play! framework, a java web framework for non-purists"]/../..//a/img/..')
    waitForTextNotPresent('Play! framework, un framework hecho en java.. para herejes')

    // Search Engines
    assertTextPresent('Search Engines')
    click('xpath=//a[text()="Search Engines"]/../..//a/img/..')
    waitForTextNotPresent('Search Engines')

    // Delete Drools and jBPM5
    assertTextPresent('Drools and jBPM5')
    click('xpath=//a[text()="Drools and jBPM5"]/../..//a/img/..')
    waitForTextNotPresent('Drools and jBPM5')
    
    // Delete Scale the Everest with Scala
    assertTextPresent('Scale the Everest with Scala')
    click('xpath=//a[text()="Scale the Everest with Scala"]/../..//a/img/..')
    waitForTextNotPresent('Scale the Everest with Scala')
    
    open('/')
    assertNotTitle('Application error')

    assertTextPresent('No events in sight...')

    clickAndWait('xpath=//a[text()="load from yaml file"]')
    
    assertTextNotPresent('No events in sight...')
    
    assertTextPresent('Play! framework, a java web framework for non-purists')
    assertTextPresent('Search Engines')
    assertTextPresent('Drools and jBPM5')
    assertTextPresent('Scale the Everest with Scala')
#{/selenium}

Running your tests from the command line

If you are planning to integrate your tests with a continuous integration server, you can run all your tests with the command line play autotest. The results of the test will be at the test-results folder.

More over, Guillaume Bort, the creator of play framework, developed a lightweight integration server with play. Here's the announcement on google group list, and it's being used with the play! framework project itself, you can check it at http://integration.playframework.org/.

More information on testing

Selenium command reference

How the play framework test runner works.

Play's documentation on testing

Cobertura test module

Mockito play module


Now we are going to Step 13 - deploy to heroku.