Objectives

Explore the Unit test features of Play by writing some tests to verify the current behavior of the User and Message classes for the spacebook app.

Simple Test Harness

You have seen until testing in Eclipse for simple Java applications. There is an equivalent for the Play Framework. Unit testing is particularly important if you make any changes to the model, as it allows you to verify if the model is defined correctly.

Using your own spacebook project - or this solution to the second assignment from last semester:

To import this to eclipse, make sure play is on your path, unzip the archive and enter these commands:

play deps
play eclipsify

.. from within the folder.

In eclipse, notice that you already have a 'test' folder, with some tests already in there:

Open the file 'BasicTest.java':

import org.junit.*;
import java.util.*;
import play.test.*;
import models.*;

public class BasicTest extends UnitTest
{
  @Test
  public void aVeryImportantThingToTest()
  {
    assertEquals(2, 1 + 1);
  }
}

Executing this is a little different from executing standard junit tests, and requires the following stages:

Stage 1: Run the spacebook app in 'test mode'

From the console, within the spacebook folder, run the app ass follows:

play test

This will respond as follows:

~        _            _ 
~  _ __ | | __ _ _  _| |
~ | '_ \| |/ _' | || |_|
~ |  __/|_|\____|\__ (_)
~ |_|            |__/   
~
~ play! 1.2.5, http://www.playframework.org
~ framework ID is test
~
~ Running in test mode
~ Ctrl+C to stop
~ 
CompilerOracle: exclude jregex/Pretokenizer.next
Listening for transport dt_socket at address: 8000
08:31:32,518 INFO  ~ Starting /Users/edeleastar/repos/modules/app-dev-modelling-prj/spacebook-3
08:31:32,522 INFO  ~ Module cloudbees is available (/Users/edeleastar/repos/modules/app-dev-modelling-prj/spacebook-3/modules/cloudbees-0.2.2)
08:31:32,523 INFO  ~ Module crud is available (/Users/edeleastar/dev/play-1.2.5/modules/crud)
08:31:33,136 WARN  ~ You're running Play! in DEV mode
~
~ Go to http://localhost:9000/@tests to run the tests
~
08:31:33,242 INFO  ~ Listening for HTTP on port 9000 (Waiting a first request to start) ...

Stage 2: Browse to the 'test runner'

In chrome, bring up a browser are the following url:

This should show this:

Be careful here - we only want to select the 'BasicTest' - so click once on it, and the page will look like this:

Stage 3: Run a successful Test

Now press 'Start' and note the changes in the page:

This has run the tests - but as the trivial test passed, then the page remained green.

Stage 4: Run a failing Test

Make a small change to the test such that it is guaranteed to fail:

  public void aVeryImportantThingToTest()
  {
    assertEquals(3, 1 + 1);
  }

Now run it again:

Note carefully the error report.

Users Test

Lets write some test for the classes we already have working. Before we do so, there is an occasional anomaly in play that can cause some confusion. So, open 'app/default package/BootStrap.java' and comment out the following:

import java.util.List;

import play.*;
import play.jobs.*;
import play.test.*;

import models.*;

@OnApplicationStart
public class Bootstrap extends Job 
{ 
  public void doJob()
  {
    //Fixtures.deleteDatabase();
    //Fixtures.loadModels("data.yml");
  }
}

We are removing the objects loaded from the yaml file for the moment. Remember to put this back in when you are finished testing.

Now introduce the following test into the BasicTest class:

public class BasicTest extends UnitTest
{
  @Test
  public void aVeryImportantThingToTest()
  {
    assertEquals(2, 1 + 1);
  }

  @Test
  public void testCreateUser()
  {
    User bob = new User("bob", "jones", "bob@jones.com", "secret", 20, "irish");
    bob.save();

    User testUser = User.findByEmail("bob@jones.com");
    assertNotNull (testUser);
  }
}

Look carefully at the 'testCreateUser()' test. In it we are creating a new user, saving it, and then seeing if we can find the user by the email address. Save and run this test.

It should succeed. Now, make a deliberate mistake in the test - try find 'alice' instead of 'bob' by making the following adjustment:

  @Test
  public void testCreateUser()
  {
    User bob = new User("bob", "jones", "bob@jones.com", "secret", 20, "irish");
    bob.save();

    User testUser = User.findByEmail("alice@jones.com");
    assertNotNull (testUser);
  }

Run tests again, you should see something like this:

Change it back to 'bob' and confirm that the error is now gone.

More User Tests

You may be surprised that, even if we are running in test mode, the 'admin' interface is still visible:

Explore the user objects now. You may notice that we seem to have more than one? If you look at the objects data - it appears that the same object 'bob' may be there several times.

This is clearly an anomaly, and is the result of running the tests repeatedly - and having leftover values as a result. This should be corrected immediately as it is very important that the tests are not influenced by whatever may be in the database.

Introduce the following method into BasicTest:

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

Now run the tests a few times - and keep an eye on the number of Users via the admin interface. There should always be only one.

Introduce the following test:

  @Test
  public void testFindUser()
  {
    User jim = new User("jim", "smith", "jim@smith.com", "secret", 20, "irish");
    jim.save();

    User test = User.findByEmail("jim@smith.com");
    assertNotNull (test);

    User alice = User.findByEmail("alice@jones.com");
    assertNull (alice);
  }  

This is an example of a 'negative' test. We are deliberately looking for a user (alice) who we know to be not there. Run it and make sure it passes.

Try to make the test deliberately fail?

Message Tests

We have another model object called Messages already defined in our project:

public class Message extends Model
{
  public String messageText;

  @ManyToOne
  public User from;

  @ManyToOne
  public User to;

  public Message(User from, User to, String messageText)
  {
    this.from = from;
    this.to = to;
    this.messageText = messageText;
  }
}

We can write some tests to see if this model is set up correctly:

  @Test
  public void testCreateMessage()
  {
    User mary = new User("mary", "colllins",  "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg = new Message (mary, joan, "Hi there - how are you");
    msg.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 1);
  }

Look carefully at the above test. It is structured as follows:

Run this test now and see if it passes. We can extend the test to see if we also get back the correct text. Put these two lines at the end of the test:

    Message message = joansMessages.get(0);
    assertEquals(message.messageText, "Hi there - how are you");

Run it again and confirm it still passes. Just to confirm that everything is operating ok with the test harness, change a single character in the message, and make sure the test fails. Then change it back so that is passes again.

To conclude, we bring in some more tests to:

Here are the tests - bring these directly into your class:

  @Test
  public void testNoMessagese()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    List<Message> joansMessages = Message.find("byTo", mary).fetch();
    assertEquals (joansMessages.size(), 0);
  }

  @Test
  public void testMultipleMessages()
  {
    User mary = new User("mary", "colllins",  "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg1 = new Message (mary, joan, "Hi there - how are you");
    msg1.save();
    Message msg2 = new Message (mary, joan, "Where are you now?");
    msg2.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 2);
    Message message1 = joansMessages.get(0);
    assertEquals(message1.messageText, "Hi there - how are you");
    Message message2 = joansMessages.get(1);
    assertEquals(message2.messageText, "Where are you now?");   
  }

Read the tests and absorb what they are testing. These tests should all pass

Class Specific Tests

Our complete test case now looks like this:

import org.junit.*;

import java.util.*;

import play.test.*;
import models.*;

public class BasicTest extends UnitTest
{

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  @Test
  public void testCreateUser()
  {
    User bob = new User("bob", "jones", "bob@jones.com", "secret", 20, "irish");
    bob.save();

    User testUser = User.findByEmail("bob@jones.com");
    assertNotNull (testUser);
  }

  @Test
  public void testFindUser()
  {
    User jim = new User("jim", "smith",  "jim@smith.com", "secret", 20, "irish");
    jim.save();

    User test = User.findByEmail("jim@smith.com");
    assertNotNull (test);

    User alice = User.findByEmail("alice@jones.com");
    assertNull (alice);
  }  

  @Test
  public void testCreateMessage()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg = new Message (mary, joan, "Hi there - how are you");
    msg.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 1);
  }

  @Test
  public void testNoMessagese()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    List<Message> joansMessages = Message.find("byTo", mary).fetch();
    assertEquals (joansMessages.size(), 0);
  }

  @Test
  public void testMultipleMessages()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg1 = new Message (mary, joan, "Hi there - how are you");
    msg1.save();
    Message msg2 = new Message (mary, joan, "Where are you now?");
    msg2.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 2);
    Message message1 = joansMessages.get(0);
    assertEquals(message1.messageText, "Hi there - how are you");
    Message message2 = joansMessages.get(1);
    assertEquals(message2.messageText, "Where are you now?");   
  }
}

This mixes in User and Message tests. We can factor these out into classes of their own. In the same package, create a class call UserTest containing the following:

import org.junit.*;

import java.util.*;

import play.test.*;
import models.*;

public class UserTest extends UnitTest
{

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  @Test
  public void testCreateUser()
  {
    User bob = new User("bob", "jones", "bob@jones.com", "secret", 20, "irish");
    bob.save();

    User testUser = User.findByEmail("bob@jones.com");
    assertNotNull (testUser);
  }

  @Test
  public void testFindUser()
  {
    User jim = new User("jim", "smith", "jim@smith.com", "secret", 20, "irish");
    jim.save();

    User test = User.findByEmail("jim@smith.com");
    assertNotNull (test);

    User alice = User.findByEmail("alice@jones.com");
    assertNull (alice);
  }  
}

and a MessageTest class:

import org.junit.*;

import java.util.*;

import play.test.*;
import models.*;

public class MessageTest extends UnitTest
{
  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  } 

  @Test
  public void testCreateMessage()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg = new Message (mary, joan, "Hi there - how are you");
    msg.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 1);
  }

  @Test
  public void testNoMessagese()
  {
    User mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    List<Message> joansMessages = Message.find("byTo", mary).fetch();
    assertEquals (joansMessages.size(), 0);
  }

  @Test
  public void testMultipleMessages()
  {
    User mary = new User("mary", "colllins",  "mary@collins.com", "secret", 20, "irish");
    mary.save();

    User joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();

    Message msg1 = new Message (mary, joan, "Hi there - how are you");
    msg1.save();
    Message msg2 = new Message (mary, joan, "Where are you now?");
    msg2.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals (joansMessages.size(), 2);
    Message message1 = joansMessages.get(0);
    assertEquals(message1.messageText, "Hi there - how are you");
    Message message2 = joansMessages.get(1);
    assertEquals(message2.messageText, "Where are you now?");   
  }

}

Each of these classes is just focused primarily on one of the model classes. You can delete the BasicTest, so your project should look like this:

And refreshing the test runner site:

Finally, if we select and run the tests - we can see an individual breakdown of each test:

Just to confirm that everything is going ok - deliberately break one of the test to see the type of report:

Revert to 'green state' again by making all tests work

Exercises

The witpress app, completed during the summer school, implements a simple blogging app with comments. Here is a slightly updated version:

To import this to eclipse, make sure play is on your path and enter:

play deps
play eclipsify

Exercise 1:

Your task is to implement a comprehensive set of unit tests to fully exercise the model classes. The following screen capture is and example of the type of tests you should attempt to implement:

You should have sufficient guidance from this lab to complete these tests. Note that the test names indicate approximately the focus on each of the tests.

Exercise 2:

Currently a Blog is not modeled - a blog just a collection of posts maintained by User. Model a Blog as a first class object, developing tests to verify the implementation as you go. Do this in two versions:

Dont worry about the UI, just focus on the tests