xelnaga/http-imposter — pull request #3 by hydra (2013-11-12 16:55:33+00:00)
Title: Support regular expressions for header values and bodies.

This is needed because sometimes values in headers and bodies are not always exactly determinable by test code, this can occur for instance if the body or a header value contains a timestamp that is precise to a millisecond.

By using regular expressions for header values and bodies you can assert that at least the format of the request is correct when you cannot be sure of the actual values.

Here's an example of how to create a request pattern with a body that matches any content and must contain an 'api-signature' header with any value.

e.g.
```
RequestPattern requestPattern = new RequestPattern(
uri: "${basePath}/${tenantId}",
method: 'POST',
headers: [
new DefaultHttpHeader('accept', 'text/plain'),
new RegexMatchingHttpHeader('api-signature', /.*/)
...
],
body: new RegexMatchingBody(/.*/)
)
```

The old way of performing an assert on the report in the test no longer works when using regular expression matching headers or bodies because the report printer just prints the values of the expression and the received data and thus the resuling strings are not equal, this is easily remedied by performing a check on the report to see if the report needs to be printed and asserted, here's a snippet from a test base class:

```
void verifyInteractions() {

Report report = imposterRemote.report()

if(shouldCompareInteractionsAndExpectations(report)) {
compareInteractionsAndExpectations(report)
}
}

private boolean shouldCompareInteractionsAndExpectations(report) {

if (!sameAmountOfExpectationsAndInteractions(report)) {
return true
}
return hasUnmatchedExpectation(report)
}

private boolean sameAmountOfExpectationsAndInteractions(report) {
report.expectations.size() == report.interactions.size()
}

private boolean hasUnmatchedExpectation(Report report) {
return report.expectations.find { Interaction expectation ->
int index = report.expectations.indexOf(expectation)
if (!expectation.request.matches(report.interactions[index].request)) {
return expectation
}
}
}

private void compareInteractionsAndExpectations(Report report) {
String actual = makeOneLine(report.interactions)
String expected = makeOneLine(report.expectations)

assert actual == expected
}
```

Old tests using RequestPattern instances will need to be updated since the headers use DefaultHttpHeader instead of HttpHeader and the body is now an object, compare old vs new below:

OLD:
```
RequestPattern requestPattern = new RequestPattern(
uri: "${basePath}/${tenantId}",
method: 'POST',
headers: [
new HttpHeader('accept', 'text/plain'),
],
body: 'plain text'
)
```

NEW:
```
RequestPattern requestPattern = new RequestPattern(
uri: "${basePath}/${tenantId}",
method: 'POST',
headers: [
new DefaultHttpHeader('accept', 'text/plain'),
],
body: new DefaultBody('plain text')
)
```

Any HttpImposterRemote should now use the Interaction serializer to send the expectation payload over to the HttpImposter controller expect action. This also negates the need for the (deleted) ByteArrayResponsePreset because the serialization/encoding/decoding is handled by Gson type adapter instances for HttpHeader and Body objects. If it turns out that is not the case then users are free to add their own implementation of the Body interface and/or register their own type adapters.

Here's a snippet:

```
package uk.co.newsint.platform.imposter

import com.google.gson.Gson
import net.xelnaga.httpimposter.model.Interaction
import net.xelnaga.httpimposter.model.Report
import net.xelnaga.httpimposter.model.RequestPattern
import net.xelnaga.httpimposter.model.ResponsePreset
import net.xelnaga.httpimposter.serializer.ReportSerializer
import net.xelnaga.httpimposter.serializer.json.JsonInteractionSerializer
import org.apache.http.Header
import org.apache.http.client.HttpClient
import org.apache.http.impl.client.DefaultHttpClient
import some.rest.Client
import some.rest.Response

import static org.apache.http.HttpStatus.SC_OK

class HttpImposterRemote {

public static final String IMPOSTER_EXPECT = "/expect"
public static final String IMPOSTER_REPORT = "/report"
public static final String IMPOSTER_RESET = "/reset"

private Gson gson

ReportSerializer reportSerializer
JsonInteractionSerializer interactionSerializer

Client restClient

HttpImposterRemote(String baseUrl, List

defaultHeaders) {

HttpClient httpClient = new DefaultHttpClient()
restClient = new Client(baseUrl, defaultHeaders, httpClient)

gson = new Gson()

interactionSerializer = new JsonInteractionSerializer()
}

void expect(RequestPattern requestPattern, ResponsePreset responsePreset) {

Interaction interaction = new Interaction(requestPattern, responsePreset)

String body = interactionSerializer.serialize(interaction)

Response restResponse = restClient.post(IMPOSTER_EXPECT, body)

if (restResponse.status != SC_OK) {
throw new HttpImposterRemoteException('Failed to set expectation')
}
}

Report report() {

Response restResponse = restClient.get(IMPOSTER_REPORT)

if (restResponse.status != SC_OK) {
throw new HttpImposterRemoteException('Failed to get interaction report')
}

return reportSerializer.deserialize(restResponse.bodyAsString)
}

void reset() {

Response restResponse = restClient.post(IMPOSTER_RESET)
if (restResponse.status != SC_OK) {
throw new HttpImposterRemoteException('Failed to reset')
}
}
}
```

Enjoy!

primary author, idea, design and review by Dominic Clifton
primary co-author: Lucas Gluchowski
additional co-author: Arsalan Ahmed