You have some service or database call. You need that call to be repeatable. Sometimes a record and playback is sufficient. You make the call, record what it returns, and play that back every subsequent time.
But often you need a bit more control. So you can create a programmable mock (or test double). The data that is returned by the call is programmed by the test. The data is not fixed, as it often is in unit testing mocks. The data comes from the test itself. Let’s take a simple example:
Given Customer is:
Customer ID | Name | Street | ZIP |
007 | James | 1 Apple Lane | 27701 |
When we change his or her address
Customer ID | 007 |
Street | 100 Penny Lane |
ZIP | 27702 |
Then customer should be:
Customer ID | Name | Street | ZIP |
007 | James | 100 Penny Lane | 27702 |
Now this test could be run against a real database. After running the test, we’d need to restore the data back to the original data in order to run it again. There are other ways of handling this (see the ATDD book for details).
Context diagram for the system
But let’s do this with mocks/test doubles for the database interface calls. Here’s the data that needs to be setup. There is no need to mock this. You just use the data object directly.
public class CustomerData { private CustomerID id; private String name; private String street; private ZIPCode zipCode; public void setID(CustomerID value) { id = value; } public void setName(String value) { name = value; } public void setStreet(String value) { street = value; } public void setZipCode(ZIPCode value) { zipCode = value; } public CustomerID getID( ) { return id ; } public String getName() { return name; } public String getStreet() { return street; } public ZIPCode getZipCode( ) { return zipCode; } public String toString() { return "Customer is " + getID() + " " + getName()+ " " + getStreet() + " " +getZipCode(); } }
And here’s the interface to the DAO that needs to be mocked.
public interface CustomerDataDAO { public CustomerData getCustomer(CustomerID id) ; public boolean putCustomer(CustomerData aCustomer); }
You create a test double class that implements this interface and adds additional methods to set and retrieve the data. Here’s an example:
public class CustomerDataDAOMock implements CustomerDataDAO { private List<CustomerData> data; public CustomerData getCustomer(CustomerID id) { // Could do a look up in the list // for this example just return first item return data.get(0); } public boolean putCustomer(CustomerData aCustomer) { // could do lookup in list, // for this example just set first item data.set(0, aCustomer); return true; } // Called by test to set the data public boolean setData(List<CustomerData> data) { this.data = data; return true; } // Called by test to return the data public List<CustomerData> getData() { return data; } }
Now in the test code (Cucumber shown here, but it could be any framework), you have:
@Given("^Customer is:$") public void customer_is(List<CustomerData> data) throws Throwable { // Hook up to a singleton factory // Service code calls this same factory // (can use autowire on Spring) Singletons.customerDataDAO = new CustomerDataDAOMock(); CustomerDataDAOMock customerDAOMock = (CustomerDataDAOMock) Singletons.customerDataDAO; customerDAOMock.setData(data); }
Now you can set the data in the mock/test double to whatever you require for the test. There are at least a couple ways a test double could be set with the test data:
1.) If the test double is created as a singleton in the same process, then the test and the production code will access the same object. The test double can be set by calls to it. Spring could be used to autowire the singleton for both the test and the production code. This is shown in the example above.
2.) If the test double is in a different process, the test could write the data to a file or an inter-process communication method (e.g. a socket, a REST service, etc.). The test double would read the file or socket.
For each service (e.g. database retrieval or external service), a test double would be written for that service. All applications could use this same test double. Ideally, the test double would be created by the same team that created the original service. That way the two would more easily be kept in synchronization.
For the Then part, you can retrieve the values from the test double if needed.
@Then("^Customer should be:$") public void customer_should_be(List<CustomerData> data) throws Throwable { // Example shows a single CustomerData object // You could compare the entire list as well CustomerID id = new CustomerID(data.get(0).getID()) CustomerData oneItem = Singletons.customerDataDAO.getCustomer(id); // Compare the retrieved item to the data in the argument }