Coding the Contract Using Test-Driven Development
Techniques
Coding the contract using agile and test-driven development techniques requires writing
a number of tests and implementing aMock URL layer.
Problem
You want to code the contract using these development techniques.
Solution
To demonstrate, let’s define a use case, implement the use case as a contract, write a test case(s)
to implement the contract, implement the contract in the Mock URL, and finally run the test.
The example is XMLHttpRequest-based, as you’ll most likely be using XMLHttpRequest for
your testing purposes. However, the contract and mock URLs are not integrated explicitly into
the client or server code, so that you can verify any client. This article is about building Web
services, and you can call Web services by an XMLHttpRequest client, by a mashup client, or by
some server-side Web service aggregator.
The example features a calculator application that contains only a single operation used
to add two numbers together. What makes the calculator operation unique is that it uses temporary
URLs to keep a history of past additions.
The use case is the addition of two numbers,
but two things must happen to carry out the use case: redirection and addition.
The details of managing redirections aren’t covered here but will be covered in the following
section entitled “Testing a Dynamic Contract.” How the temporary URL is determined falls
into the category of pixie dust, so let’s focus on the details of the contract that performs the
addition. The following represents the HTTP request used to perform an addition:
POST /services/calculatorrest/operations/2364564565 HTTP/1.1
Content-type: application/json
User-Agent: Jakarta Commons-HttpClient/3.0
Host: localhost:8100
Content-Length: 25
In the request, an HTTP POST is executed, and the URL used is the temporary URL found
in the redirection test. The HTTP headers Content-type and Content-Length are not optional
and are used to define the type and length of the content sent with the POST request. The body
of the request contains a buffer encoded using JavaScript Object Notation (JSON).13 Two data
members are defined in the JSON request: number1 and number2. These two data members represent
the numbers to be added.
The following shows the appropriate response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 14
Server: Jetty(6.0.x)
{"result":3}
The headers Content-Type and Content-Length describe the content that is returned, which
is encoded using JSON and contains a single data member. The single data member result is
the result of adding two numbers together.
As you look at the requests and responses and do some mental math, you’ll know that
adding 1 and 2 results in the sum of 3. From the perspective of the contract, it would seem that
everything is OK and that the system is implemented and works. In reality, however, the illustrated
HTTP conversations were all faked. This leads to the following question: when developing
a contract, how do you physically define the contract?
A purist might say, “The contract is defined using some sort of tool that the client and
server programmers then implement.” The purist answer sounds good and would be great if
such a tool existed. Unfortunately, no tool allows you to design REST-based HTTP conversations
that can serve as the basis of the test and Mock URL layer.
Faced with the fact that you don’t have such a tool, using an editor to generate the HTTP
conversation manually is incredibly error-prone and tedious. I don’t suggest that anybody
should do that. However, you still need to define a contract, but you can’t do it without a tool
or an editor. Without any sort of documentation, the contract literally remains a figment of
your imagination.
And figments of imagination are rather difficult to create test cases for.
The solution is to write the tests that make up the contracts, even though writing a bunch
of tests does not make a contract. The tests have to serve a dual purpose that is, they have to
provide tests as well as act as documentation for how the contracts are called and used. Therefore,
it’s important that you structure the tests clearly and make them verbose.
For programming purposes, you can code the test framework in any particular language
that makes you comfortable. For the scope of this solution and this article, I use JavaScript and
JsUnit to write my test scripts. However, there’s nothing stopping you from using Java, a .NET
language, PHP, or Ruby.
You can start the contract in one of two ways: You can implement the client side first, or
you can implement the Mock URL layer first. If you implement the client side first, you’d be
using a top-down approach, and if you implement the Mock URL side first, you’d be using
a bottom-up approach. I’ve chosen a top-down approach and have implemented the client
side first. Of course, many say that top-down is the best, but I try not to be so picky about it.
The important thing is that you start with one, do a bit of coding, and then move to the other.
Don’t code all of one and then implement the other. Remember that you’re trying to develop
the contract in an agile manner using test-driven development techniques.
The generated HTML page contains two buttons. You use the topmost button Run All
Tests to run all available tests on the HTML page. You use the lower button Test Prototype to
run a particular test. When you click the button, the status of the test is generated to the right
of the button. In the example, the status of the test is “not run.” Below the button is trace output,
which is used to display any messages or errors that occur as the tests are running.
The source of the HTML page is defined as follows.
Source: /jaxson/trunk/website/ROOT/scripts/templates/testcontract.html
<html>
<head>
<title>Contract Test Page</title>
<script language="JavaScript" src="/scripts/common.js"></script>
<script language="JavaScript" src="/scripts/Synchronous.js"></script>
<script language="JavaScript" src="/scripts/commontest.js"></script>
<script language="javascript" src="/scripts/jsunit/jsUnitCore.js"></script>
</head>
<body>
<script language="javascript">
// Setup the output generator
setJsUnitTracer( new jsUnitTraceGenerator( "traceoutput"));
// Start of defined contract URLs
// Potentially define a URL as
// var baseURL = "/my/url";
// End of defined contract URLs
var testsToRun = {
// Start JavaScript code for test cases here
testPrototype : function() {
// Synchronous functions identically to Asynchronous
// but Synchronous waits for the request to complete
// Good for testing, but bad for production as the browser hangs
var request = new Synchronous();
request.complete = function( statusCode, statusText,
responseText, responseXML){
// Do something with the result
// Indicate that you are done, and define the output element
testManager.success( "statusPrototype");
}
// Do something with the request
}
// End JavaScript code for test cases
};
testManager.setTestCases( testsToRun);
</script>
<table>
<tr>
<td><h2>Available Tests</h2></td>
<td></td>
</tr>
<tr>
<td>
<input onclick="testManager.runAll()"
type="button" value="Run All Tests" />
</td>
<td></td>
</tr>
<tr>
<td>Test</td>
<td>Status</td>
</tr>
<!-- Insert GUI for test cases here -->
<tr>
<td>
<input onclick="testManager.testPrototype()" type="button"
value="Test Prototype" />
</td>
<td id="statusPrototype">Not run</td>
</tr>
<!-- End test cases here -->
</table>
hr />
table border="1">
<tr>
<td><h2>Trace output</h2></td>
</tr>
<tr>
<td id="traceoutput"></td>
</tr>
</table>
</body>
</html>
The code is relatively long, so I’ve highlighted the important pieces to make it simpler to
understand. The test code uses the script tag to include a number of JavaScript files that provide
the basis of the test code. After you load the base script, the first piece of highlighted code
setJsUnitTracer redirects the generated warnings and informational and debug messages to
the current HTML page. Specifically, the output is generated in the table element with the id
traceoutput, which is shown in bold at the bottom of the HTML code.
The other bold source code is the variable baseURL, which represents a variable that references
the contract URLs that will be used in the test script. It’s important to define all contract
URLs in this area so that it’s clear which URLs need to be supported. If a URL cannot be determined
ahead of time because of its dynamic nature, then you declare a variable and assign an
empty string.
The next pieces of bold text represent a variable (testsToRun) and a function (testPrototype).
The function testPrototype is an example of how you could write a test. Remember these two
important steps: Use Synchronous, and call the method testManager.success. Synchronous is
a helper class that makes a synchronous XMLHttpRequest call. Normally, in your applications
you would use the Asynchronous class, which makes asynchronous XMLHttpRequest calls.
Let’s take a look at the differences between asynchronous and synchronous requests and
discuss why you’d choose either.
When XMLHttpRequest is used to make a synchronous
request, XMLHttpRequest waits for a response before returning control to the browser. Having
XMLHttpRequest wait for an answer is a problem, because JavaScript is not multithreaded,
causing the browser to lock. For a better user experience, you should always use asynchronous
requests. Using asynchronous requests has its own problems. An asynchronous request doesn’t
wait for the response and returns control to the JavaScript. When running tests, asynchronous
requests are a problem because tests are executed sequentially, not concurrently. When writing
Ajax and REST code, use the following rule of thumb: use asynchronous requests for applications,
and use synchronous requests for testing.
Getting back to the example function testPrototype, each test function must indicate
whether or not the test was successful.
This is required because when the test manager runs
all tests, the next test will run only when the current test was successful. In the example, a success
is when the method testManager.success is called. Calling the method failed indicates
a failure, and the method waiting indicates that the test involves multiple steps and needs to
wait for an answer before determining success or failure.
Moving on, in the example HTML source code file, the method testManager.setTestCases
associates the tests with the test manager. The test manager iterates through all of the tests
defined in the variable testsToRun and creates a proxy that is an encapsulation to the originally
defined method. You can read more about this in Article 2. When running tests, don’t reference
the variable testsToRun, but rather the test manager variable testManager.
After the test manager method setTestCases completes initialization, you can execute the
tests. In the example HTML source code, tables (<table>) are dynamically defined to contain
references to the tests. The first table contains buttons used to execute the tests as a group or
individually. To run all of the tests, call the method testManager.runAll(). To run an individual
test, execute the method testManager.[testname]. The second table is used for generating
the logging output.
Let’s first test the redirection and addition, as illustrated by the following code snippet.
Source: /jaxson/trunk/website/ROOT/calculator/testcontract.html
var entityURL = "";
var testsToRun = {
testVerifyAdd : function(){
info( "testVerifyAdd", "Running testVerifyRedirection first");
testsToRun.testVerifyRedirection();
info( "testVerifyAdd", "Finishing running testVerifyRedirection");
var state = new Object();
state.number1 = 1;
state.number2 = 2;
var buffer = JSON.stringify( state);
info( "testVerifyAdd", "JSON Buffer (" + buffer + ")");
var request = new Synchronous();
request.complete = function( statusCode, statusText,
responseText, responseXML) {
var response = JSON.parse( responseText);
info( "testVerifyAdd.complete", "Add Result (" + responseText + ")");
assertEquals( "JSON result", 3, response.result);
testManager.success( "addTest");
}
request.post( entityURL, "application/json", buffer.length, buffer);
}
};
...
<tr>
<td>
<input onclick="testManager.testVerifyAdd()"
type="button" value="Test Add" /></td>
<td id="addTest">Not run</td>
</tr>
The code snippet illustrates the JavaScript code that contains the test, and the HTML
snippet shows how to call the test defined by the JavaScript. The variable entityURL references
the contract URL used to perform an addition. The variable is not assigned a predefined URL,
because the URL is created dynamically in another test not illustrated in the snippet. Contained
within the definition of the variable testsToRun is a function testVerifyAdd that represents the
test used to perform an addition.
To run the test, the test manager calls the dynamically defined method testManager.
testVerifyAdd, which calls testsToRun.testVerifyAdd. The purpose of creating a proxy to the
test is to enable the test manager to manage the test harness that calls the test.
In the implementation of testVerifyAdd, the not-illustrated test testVerifyRedirection is
called. The testVerifyRedirection executes to verify that the variable entityURL will reference
a valid URL. Notice how the not illustrated test is referenced using the variable testsToRun and
not testManager.
Earlier, I stated that you should call testManager to run a test, and not the test
directly. That rule of thumb only applies if you want to run a test and are not already running
a test. The main reason why you wouldn’t use testManager is that if an exception is generated,
you want the currently running test to exit. Calling a test using testManager within a test results
in the exception being caught and the current test continuing as if everything went OK. Of
course, this doesn’t mean that you might not want this behavior, and you could call a test from
testManager. The choice is yours, but it’s more important to understand the reason for calling
each.
Having called the not-illustrated testVerifyRedirection test, the test of performing an
addition has started. The test will verify that adding 1 and 2 results in the value of 3. The data
is stored in a JavaScript object instance state that you instantiate and assign. You serialize the
state of the object to the JSON format using the method JSON.stringify.
Once you convert the state into a string buffer, you send it to the server using the method
request.post. The method request.post is an HTTP POST request, fulfilling the requirements
of REST. When the request.post method responds, the method request.complete is called. In
the anonymous method implementation of request.complete, the returned buffer is formatted
using JSON. To convert the JSON buffer into a state, you call the method JSON.parse. You
assign the returned state to the variable response. The state contains the value of the addition,
and the value is tested using the method assertEquals. If the value is not 3, then the testing
framework triggers, catches, and processes an exception.
With either example, you can see which tests were successful and which tests failed. When
a test fails, an error states why the test failed. Additionally, informational messages are generated
so that you know what your tests are doing and what data is being sent.
The test is written as a single test and doesn’t constitute a complete contract test. When
you implement the client-side tests of your contract, you’ll want to employ test-driven development
techniques that include tests that succeed and tests that fail.
Now let’s shift focus from the client side to the server side. You don’t want to implement
a complete working server-side implementation, but rather implement the Mock URL layer.
For the scope of this solution, Java is used.
The purpose of the Mock URL layer is to imitate and implement server-side functionality.
Imitating and implementing server-side functionality is tricky, because you can only implement
targeted test cases. In the case of the example, that means implementing the case of
adding 1 and 2. Of course, the addition of 1 and 2 is trivial, and in the case of the Mock URL,
you could implement it within seconds. However, there are more complicated cases, so you
shouldn’t be tempted to provide a solution.
The focus of the Mock URL layer is to provide correct requests and responses for specific
tests. By implementing logic, you’re setting yourself up for an error because the logic needs to
be tested. Let’s put it this way: Imagine implementing the trivial addition of two numbers.
How do you know that your implementation will work properly? The answer is that you write
tests. However, that doesn’t answer the question properly, because how do you know that the
tests are implemented properly? The answer is that you don’t, and that is the purpose of the
Mock URL layer.
The following source code implements the request and response of the add contract.
Source: /jaxson.java.tests/devspace/jaxson/tests/calculator/mockurl/DoMockAdd.java
public class DoMockAdd extends MockUrlTestCaseBase {
public void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
assertAreEqualJSONObject( request, "requestadd.json");
try {
FileWriter.writeFileObject( "application/json",
response, "responseadd.json");
}
catch( Exception e) {
throw new ServletException( "Could not write file response", e);
}
response.setStatus( 200);
}
}
Two method calls are shown in bold. The method assertAreEqualJSONObject compares
the sent data stored by the Java servlet to the file requestadd.json. If the sent data matches the
contents of the file, then the next method call writeFileObject is executed. The purpose of
writeFileObject is to send the contents of the file responseadd.json to the client. The files
requestadd.json and responseadd.json represent predefined contracts used to add two numbers
together.
The class DoMockAdd has no idea what the purpose of the operation is. It only knows that if
the request matches a file, then a response based on another file is sent. The Mock URL layer
implementation is simple but can only deal with a single case of adding two specific numbers
and generating a single response. In the Mock URL layer example, you perform multiple tests
to see which test case is matched. If a test case is matched, then the appropriate response is
sent. If no test cases matches, then no response is sent, and an error is generated.
The method assertAreEqualJSONObject compares the JSON data that is sent to the JSON
data in a file. Don’t be misled into believing that a byte-to-byte comparison is performed. The
method assertAreEqualJSONObject performs a logical comparison based on the format of the
data. This is important because otherwise, whitespace or other characters that don’t influence
the state of the data could cause a test to fail. You don’t want a test failing because of a different
formatting, unless of course you desire testing a specific formatting of the data. For example,
XML is another technology where most likely you don’t want whitespace to cause the test to fail.
After you’ve created the Mock URL layer, you can test the client scripts, which will verify
the contracts. Based on the working client and server side, subbing in a working client or
server implementation should not change the behavior. If the behavior is changed, then the
client test scripts and Mock URL implementations are inconsistent. You want neither the
client nor the server to know if it’s running against a test or an actual implementation.
You should remember the following points when creating contracts:
• The Mock URL layer represents the definitive contract between the client and server.
• The Mock URL layer implements the contracts using predefined files for requests and
responses.
• The Mock URL layer can only test targeted test cases and should not use any code that
will be used in implementation, since the logic might have bugs.
• When comparing the sent data with the data in the file, use a logical comparison and
not a byte-to-byte comparison. A byte-to-byte comparison could cause whitespace,
which has nothing to do with the state of an object, and would cause a test to fail. The
exception to this rule is if the test requires verifying the whitespace.
• The Mock URL layer performs multiple tests on which request is being sent and sends
the appropriate response. In most cases, the request is tested using a file, and the
response is based on another file.
• If you must implement logic in the Mock URL layer, make sure that it’s extremely well
tested and stable, as that implementation will serve as a reference for how the contract
between the client and server functions.
• The client-side tests that test the contract represent an implementation of how to use
the contract and are used to verify the correctness of the server-side implementation.
• You develop the client-side contract tests and Mock URL layer together using testdriven
development techniques.
• You can implement the client-side contract tests in any programming language, but
since this article and most likely your application are JavaScript-based, it makes sense to
use JavaScript.
• Neither the client- nor the server-side implementations or tests should ever have
any dependencies on each other. This way, you can replace the client tests with the
client implementation without causing problems in the Mock URL layer or server
implementation.
1-4.Testing a Dynamic Contract
The previous example, which illustrated how to create a contract, didn’t cover the dynamic
aspect of the contract and the problem of redirection in particular. Redirection was not covered
because redirection is a part of a bigger problem that is part of the Ajax and REST
paradigm.
Problem
You want to test a contract that is dynamic.
Solution
The “Understanding the Definition and Philosophy of Ajax” section argued that Ajax allows
you to create and manipulate content dynamically. The dynamism extends to the contract,
which can involve the following techniques:
• Definition of a specific URL based on a general URL
• Definition of specific content based on a specific URL
In either example, a general URL or general content reference is hard-coded or referenced
in the client side. The hard-coded general reference is then converted into a specific
reference. To understand what is involved, let’s focus on the calculator example and the redirection
part of the addition operation. The following code represents the HTTP request that
the client would make to convert the general addition operation URL into a specific addition
operation URL:
GET /services/calculatorrest/operations HTTP/1.1
User-Agent: Jakarta Commons-HttpClient/3.0
Host: localhost:8100
An HTTP GET is executed, and the URL /services/calculatorrest/operations is called
using the HTTP 1.1 protocol. This part of the request is required. In the example, the HTTP
headers are not required, but to implement the Permutations14 pattern, the headers most
likely are required. For this test, the required response is as follows:
HTTP/1.1 201 Redirecting+a+user
Location: /services/calculatorrest/operations/2364564565
The response looks a bit odd, because the HTTP code 201, and not 307 or 302, is returned.
For those readers who have no idea what the response codes mean, let me clarify. If you make
a request and the server wants to redirect you to the real temporary URL, then you use a response
code in the 3xx series. In the case of the calculator application, the redirect is temporary, as
many addition operations could be performed. Therefore, the appropriate response would be
either 307 or 302. However, that is not the correct answer for multiple reasons.
Returning either a 307 or 302 is not correct in this case for the following reasons:
• The browser makes the redirection automatically and thus doesn’t give the XMLHttpRequest
object the redirected URL.
• Doing an automatic redirection is not useful, because you may want to execute multiple
queries and would not want to perform multiple redirections.
• When making a request on the base URL, you’re not doing a redirect to a known resource,
but rather creating a resource that you need to redirect to.
In the World Wide Web Consortium (W3C) HTTP 1.1 specification, the 201 response code
is used to indicate that calling the original URL has created a new resource that can be referenced
at the new URL, which is defined in the Location HTTP header. Therefore, even though
you could have used 307 or 302, the more appropriate answer is 201.
From an implementation perspective, the identifier 2364564565 is generated dynamically
and cannot be predicted. From a testing perspective, this is a problem because you cannot write
a test for the identifier 2364564565. If you were to do so, you’d violate the principle of being
able to substitute the Mock URL layer for a server implementation. The reason is because the
client test would expect a specific identifier that the server implementation cannot, nor should,
generate.
The solution is not to test for a specific identifier, but rather to test for the existence and
format of the identifier, as illustrated by the following test.
Source: /jaxson/trunk/website/ROOT/calculator/testcontract.html
var baseURL = "/services/calculatorrest/operations";
var entityURL = "";
testVerifyRedirection : function(){
var request = new Synchronous();
request.complete = function( statusCode, statusText,
responseText, responseXML){
if ( statusCode != 201) {
fail( "Expected 201 received " + statusCode);
}
entityURL = this._xmlhttp.getResponseHeader( "Location");
if ( entityURL == null || entityURL.length <= baseURL.length) {
fail( "Redirected URL cannot be null");
}
info( "testVerifyRedirection", "Redirected URL is (" + entityURL + ")");
testManager.success( "urltest");
}
request.get( baseURL);
},
In the test, the hard-coded reference URL is stored in the variable baseURL. The dynamically
created URL is stored in the variable entityURL, which is assigned an empty string. The
test testVerifyRedirection has a single purpose and that is to call the hard-coded general reference.
Calling the hard-coded general reference returns the specific dynamic reference. To
implement the contract, you must test two things. The first is the return of the status code 201,
and the second is the generation of the identifier. Testing for the status code 201 is simple and
involves a decision.
Testing for the dynamic identifier is a bit more complicated, but the approach taken in
the test is simple. The test contains two verifications to test the existence of the dynamically
generated identifier. The two verifications are the two lowest layers of testing a dynamic identifier.
The following lists the verifications from the lowest to highest level of verifiability:
• Testing for the existence of the data: Typically, testing for the existence is a null or notnull
test. If the test is not null, it doesn’t mean that the data is correct, but it does verify
that there is data. The test assumes that the data contains the dynamic identifier.
• Testing for the existence of the identifier in the data: Testing for the existence means
knowing about the nature of the dynamic identifier. Typically, that means knowing
what the original data is and how the dynamically generated data should appear.
• Testing the formatting of the dynamically generated identifier: Testing the formatting
means knowing something about the format of the identifier. This could mean knowing
that the identifier is numeric, a certain length, or must contain certain characters. The
calculator example doesn’t test for the correct formatting of the dynamically generated
identifier, but if it had, the test would have been length-based and numerically based.
Be careful when testing for the formatting, as the dynamic generation of temporary
data might switch from one version to another.
When testing dynamic data, start at the lowest level and perform the tests incrementally.
Don’t start at the highest level right from the start. Doing so makes a dangerous assumption
that dynamic data exists. For example, if you only test for correct formatting, you won’t be able
to discern between the test failure of missing dynamic data and incorrectly formatted dynamic
data.
The following code illustrates a Java implementation of the URL redirection:
public class RedirectionImplementation extends MockUrlTestCaseBase {
public class RedirectionImplementation extends MockUrlTestCaseBase {
public void processRequest(HttpServletRequest request,
HttpServletResponse response) {
this.generateRedirection( response, 201,
request.getRequestURI() + "/2364564565");
}
}
The bold code illustrates how the redirection is implemented. Notice that the dynamic
identifier is hard-coded. There is no logic. If the same client called the redirection multiple
times, it would receive the same identifier. This appears to be a violation of the contract, yet it
is not a violation. The contract for the calculator says to redirect to a resource that you can use
to perform a calculation. The client cannot make assumptions, but the server can, because the
server is in control of generating the dynamic identifiers. To turn the table, if the client has the
responsibility to define the dynamic identifier, then the server must accept the dynamic identifier
from the client and use it for Mock URL purposes.
You could generate the dynamic identifier dynamically, but how would you test the correctness
of the contract? This goes back to the problem illustrated in the previous section,
which said the Mock URL layer has a dual role of defining what the contract needs to look like.
If the Mock URL layer contains logic that is reused in the server implementation, then a correctness
problem can exist. Therefore, if you must have variety in dynamic identifiers, create
a few of them and then use a random number algorithm to choose between them.
When testing dynamic contracts, remember the following points:
• Figure out who is responsible for generating the dynamic data and who consumes the
dynamic data.
• The generator of dynamic data can define specific test cases and make assumptions
about how the data is formatted.
• The consumer of the dynamic data is responsible for receiving the dynamic data and
applying three levels of verification when verifying the correctness of the data.
• Test cases don’t always support dynamic data. For example, the provider might not support
a reference made to a data format. In this case, the generator of the dynamic data
must generate an error, and the consumer must verify that an error is generated. Don’t
attempt to accommodate with warnings or informational messages. You would never
expect a plumber to know how to fix a broken tooth.
|