From b0e0d5e74af82a73e70e50a472043b97cb59a483 Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Fri, 2 Mar 2018 14:39:22 -0300 Subject: [PATCH 1/6] Add dynamic values with string keys for response jsons --- .../assets/fixtures/json_error_template.json | 3 + .../assets/fixtures/json_template.json | 3 + .../requestmatcher/RequestMatcherRule.java | 112 +++++++++++++++--- .../test/RequestMatcherRuleTest.java | 63 ++++++++++ .../fixtures/json_error_template.json | 3 + .../resources/fixtures/json_template.json | 3 + 6 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 requestmatcher/src/androidTest/assets/fixtures/json_error_template.json create mode 100644 requestmatcher/src/androidTest/assets/fixtures/json_template.json create mode 100644 requestmatcher/src/test/resources/fixtures/json_error_template.json create mode 100644 requestmatcher/src/test/resources/fixtures/json_template.json diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json new file mode 100644 index 0000000..beab751 --- /dev/null +++ b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json @@ -0,0 +1,3 @@ +{ + "error_message": "{error}" +} \ No newline at end of file diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_template.json new file mode 100644 index 0000000..a9102cb --- /dev/null +++ b/requestmatcher/src/androidTest/assets/fixtures/json_template.json @@ -0,0 +1,3 @@ +{ + "current_date": "{current_date}" +} \ No newline at end of file diff --git a/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java b/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java index ef0be8a..a08cf31 100644 --- a/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java +++ b/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java @@ -126,7 +126,7 @@ public MockWebServer getMockWebServer() { public String readFixture(final String fixturePath) { try { return IOReader.read(open(fixturesRootFolder + "/" + fixturePath)) + "\n"; - } catch (IOException e) { + } catch (IOException | IllegalArgumentException e) { throw new RuntimeException("Failed to read asset with path " + fixturePath, e); } } @@ -187,25 +187,32 @@ public IfRequestMatches addFixture(String fixturePath) { */ public IfRequestMatches addFixture(int statusCode, String fixturePath) { - final MockResponse mockResponse = new MockResponse() - .setResponseCode(statusCode) - .setBody(readFixture(fixturePath)); - - if (guessMimeType) { - final String mimeType = IOReader.mimeTypeFromExtension(fixturePath); + String body = readFixture(fixturePath); + return addResponse(prepareMockResponse(statusCode, fixturePath, body)); + } - if (mimeType != null) { - mockResponse.addHeader("Content-Type", mimeType); - } - } + /** + * Adds a template to be used during the test case, later turning it into a fixture to be used. + * + * @param templatePath The path of the fixture inside the fixtures folder. + * @return A dsl instance {@link IfRequestMatches} for chaining + */ + public DynamicIfRequestMatches addTemplate(String templatePath) { + return addTemplate(200, templatePath); + } - if (!defaultHeaders.isEmpty()) { - for (String headerKey : defaultHeaders.keySet()) { - mockResponse.addHeader(headerKey, defaultHeaders.get(headerKey)); - } - } + /** + * Adds a template to be used during the test case, later turning it into a fixture to be used. + * + * @param templatePath The path of the fixture inside the fixtures folder. + * @param statusCode The status of the mocked response. + * @return A dsl instance {@link IfRequestMatches} for chaining + */ + public DynamicIfRequestMatches addTemplate(int statusCode, String templatePath) { - return addResponse(mockResponse); + String templateBody = readFixture(templatePath); + return new DynamicIfRequestMatches<>(new RequestMatchersGroup(), dispatcher, + templateBody, prepareMockResponse(statusCode, templatePath, "")); } /** @@ -258,6 +265,54 @@ public T ifRequestMatches() { } } + public static class DynamicIfRequestMatches extends IfRequestMatches { + + private final HashMap values = new HashMap<>(); + private final String templateBody; + private final MockResponse response; + private final MatcherDispatcher dispatcher; + + private DynamicIfRequestMatches(T group, MatcherDispatcher dispatcher, + String templateBody, MockResponse mockResponse) { + super(group); + this.templateBody = templateBody; + this.dispatcher = dispatcher; + this.response = mockResponse; + } + + @Override + public T ifRequestMatches() { + return dispatchDynamic(super.ifRequestMatches(), values); + } + + public DynamicIfRequestMatches withValueForKey(String key, String value) { + values.put(key, value); + return this; + } + + private T dispatchDynamic(T group, HashMap values) { + response.setBody(replaceValues(values)); + dispatcher.addFixture(response, group); + return group; + } + + private String replaceValues(HashMap values) { + String body = templateBody; + + for (String key : values.keySet()) { + String keyFinder = "${" + key + "}"; + + if (!body.contains(keyFinder)) + fail("Could not find any template key named " + key); + + body = body.replace(keyFinder, values.get(key)); + } + + return body; + } + + } + private void after(Exception exception, boolean success) throws Exception { if (dispatcher.getAssertionException() != null) { @@ -324,4 +379,27 @@ public void evaluate() throws Throwable { } }; } + + private MockResponse prepareMockResponse(int statusCode, String path, String body) { + + final MockResponse mockResponse = new MockResponse() + .setResponseCode(statusCode) + .setBody(body); + + if (guessMimeType) { + final String mimeType = IOReader.mimeTypeFromExtension(path); + + if (mimeType != null) { + mockResponse.addHeader("Content-Type", mimeType); + } + } + + if (!defaultHeaders.isEmpty()) { + for (String headerKey : defaultHeaders.keySet()) { + mockResponse.addHeader(headerKey, defaultHeaders.get(headerKey)); + } + } + + return mockResponse; + } } diff --git a/requestmatcher/src/test-common/java/br/com/concretesolutions/requestmatcher/test/RequestMatcherRuleTest.java b/requestmatcher/src/test-common/java/br/com/concretesolutions/requestmatcher/test/RequestMatcherRuleTest.java index e4882f1..a7b1548 100644 --- a/requestmatcher/src/test-common/java/br/com/concretesolutions/requestmatcher/test/RequestMatcherRuleTest.java +++ b/requestmatcher/src/test-common/java/br/com/concretesolutions/requestmatcher/test/RequestMatcherRuleTest.java @@ -8,6 +8,7 @@ import org.junit.rules.TestRule; import java.io.IOException; +import java.util.Date; import java.util.concurrent.TimeUnit; import br.com.concretesolutions.requestmatcher.RequestMatcherRule; @@ -799,4 +800,66 @@ public void informsAboutNotUsedFixture() throws IOException { client.newCall(request0).execute(); // will fail with message of non used matchers } + + @Test + public void canReplaceValuesInTemplate() throws IOException { + + String timestamp = String.valueOf(new Date(1519775413753L).getTime()); + + server.addTemplate("json_template.json") + .withValueForKey("current_date", timestamp) + .ifRequestMatches(); + + this.request = new Request.Builder() + .url(server.url("/get")) + .get() + .build(); + + final Response resp = client.newCall(request).execute(); + + assertThat(resp.isSuccessful(), is(true)); + assertThat(resp.headers().get("Content-Type"), is("application/json")); + assertThat(resp.peekBody(1_000_000).source().readUtf8(), + containsString("\"current_date\": \"1519775413753\"")); + } + + @Test + public void canReplaceValuesInTemplateWithNonSuccessfulResponse() throws IOException { + + server.addTemplate(404, "json_error_template.json") + .withValueForKey("error", "Resource not found") + .ifRequestMatches(); + + this.request = new Request.Builder() + .url(server.url("/get")) + .get() + .build(); + + final Response resp = client.newCall(request).execute(); + + assertThat(resp.code() == 404, is(true)); + assertThat(resp.headers().get("Content-Type"), is("application/json")); + assertThat(resp.peekBody(1_000_000).source().readUtf8(), + containsString("\"error_message\": \"Resource not found\"")); + } + + @Test + public void failsIfTemplateDoesNotHaveGivenKeys() throws IOException { + + exceptionRule.expect(AssertionError.class); + exceptionRule.expectMessage(containsString("Could not find any template key named something")); + + server.addTemplate("body.json") + .withValueForKey("something", "something") + .ifRequestMatches(); + + this.request = new Request.Builder() + .url(server.url("/get")) + .get() + .build(); + + client.newCall(request).execute(); + } + + } diff --git a/requestmatcher/src/test/resources/fixtures/json_error_template.json b/requestmatcher/src/test/resources/fixtures/json_error_template.json new file mode 100644 index 0000000..a74dbe1 --- /dev/null +++ b/requestmatcher/src/test/resources/fixtures/json_error_template.json @@ -0,0 +1,3 @@ +{ + "error_message": "${error}" +} \ No newline at end of file diff --git a/requestmatcher/src/test/resources/fixtures/json_template.json b/requestmatcher/src/test/resources/fixtures/json_template.json new file mode 100644 index 0000000..29d915e --- /dev/null +++ b/requestmatcher/src/test/resources/fixtures/json_template.json @@ -0,0 +1,3 @@ +{ + "current_date": "${current_date}" +} \ No newline at end of file From 053f948c988adb3f68fa5b8497ab3e6cf876a32f Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Fri, 2 Mar 2018 14:43:05 -0300 Subject: [PATCH 2/6] Fix return type in Javadoc --- .../concretesolutions/requestmatcher/RequestMatcherRule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java b/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java index a08cf31..368c4f2 100644 --- a/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java +++ b/requestmatcher/src/main/java/br/com/concretesolutions/requestmatcher/RequestMatcherRule.java @@ -195,7 +195,7 @@ public IfRequestMatches addFixture(int statusCode, String * Adds a template to be used during the test case, later turning it into a fixture to be used. * * @param templatePath The path of the fixture inside the fixtures folder. - * @return A dsl instance {@link IfRequestMatches} for chaining + * @return A dsl instance {@link DynamicIfRequestMatches} for chaining */ public DynamicIfRequestMatches addTemplate(String templatePath) { return addTemplate(200, templatePath); @@ -206,7 +206,7 @@ public DynamicIfRequestMatches addTemplate(String template * * @param templatePath The path of the fixture inside the fixtures folder. * @param statusCode The status of the mocked response. - * @return A dsl instance {@link IfRequestMatches} for chaining + * @return A dsl instance {@link DynamicIfRequestMatches} for chaining */ public DynamicIfRequestMatches addTemplate(int statusCode, String templatePath) { From f084334b3e464004d6143ad5b24f22b2aa161104 Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Tue, 6 Mar 2018 11:20:45 -0300 Subject: [PATCH 3/6] Add missing dollar sign to androidTest fixtures --- .../src/androidTest/assets/fixtures/json_error_template.json | 2 +- .../src/androidTest/assets/fixtures/json_template.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json index beab751..a74dbe1 100644 --- a/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json +++ b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json @@ -1,3 +1,3 @@ { - "error_message": "{error}" + "error_message": "${error}" } \ No newline at end of file diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_template.json index a9102cb..29d915e 100644 --- a/requestmatcher/src/androidTest/assets/fixtures/json_template.json +++ b/requestmatcher/src/androidTest/assets/fixtures/json_template.json @@ -1,3 +1,3 @@ { - "current_date": "{current_date}" + "current_date": "${current_date}" } \ No newline at end of file From 50e1718f774bb065e4cde08f2f570fc93c8bb7bc Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Wed, 7 Mar 2018 14:01:30 -0300 Subject: [PATCH 4/6] Update README to include templating usage --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 71163cf..0e488c9 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,10 @@ To add a fixture all you have to do is call one of the `addFixture` methods in t serverRule.addFixture(200, "body.json"); ``` -This will add a response with status code 200 and the contents of the file `body.json` as the body. This file, by default, must be located in a folder with name `fixtures`. This folder works different for Local Tests and Instrumented Tests. +This will add a response with status code 200 and the contents of the file `body.json` as the body. This file, by default, must be located in a folder with name `fixtures`. This folder works differently for Local Tests and Instrumented Tests. - Local tests: these are run locally in the JVM (usually with Robolectric) and follow different conventions. Your source folder `test` may contain a folder `java` and a folder `resources`. When you compile your code it takes everything in the `resources` folder and puts in the root of your `.class` files. So, your fixtures folder must go inside `resources` folder. -- Instrumented tests: there are run in a device or emulator (usually with Espresso or Robotium). It follows the android folder layout and so you may have an assets folder inside your `androidTest` folder. Your `fixtures` folder must go there. +- Instrumented tests: these are run on a device or emulator (usually with Espresso or Robotium). It follows the android folder layout and so you may have an assets folder inside your `androidTest` folder. Your `fixtures` folder must go there. Because of these differences, there are two implementations of `RequestMatcherRule`: `LocalTestRequestMatcherRule` and `InstrumentedTestRequestMatcherRule`. You should use the generic type for your variable and instantiate it with the required type. Example: @@ -141,6 +141,33 @@ The difference is that when we run an InstrumentedTest, we must pass the instrum More on the difference between each kind of test [here](https://medium.com/concrete-solutions/android-local-or-instrumented-tests-9da545af7777#.mmowgemc4) +#### Fixture templating + +You can also provide templates for cases when you need dynamic values to be passed to the JSON. For +this to work you need to provide a JSON file with keys that will be replaced by their values in runtime. + +A valid JSON template is: + +``` json +{ + "current_date": "${current_date}" +} +``` + +And it can be used like this: + +``` java + +String timestamp = String.valueOf(new Date(778338900L).getTime()); + +server.addTemplate("json_template.json") + .withValueForKey("current_date", timestamp) + .ifRequestMatches("/get_current_date"); + +// interact with server and make response assertions + +``` + ## Configuring the `RequestMatcherRule` It is possible to pass some parameters to the server rule's constructor: From 1d21c8272f4334cccb1de24f8dee07c8262f996f Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Tue, 20 Mar 2018 19:24:32 -0300 Subject: [PATCH 5/6] Adding blank lines to the end of json files --- .../src/androidTest/assets/fixtures/json_error_template.json | 2 +- .../src/androidTest/assets/fixtures/json_template.json | 2 +- .../src/test/resources/fixtures/json_error_template.json | 2 +- requestmatcher/src/test/resources/fixtures/json_template.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json index a74dbe1..32eac81 100644 --- a/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json +++ b/requestmatcher/src/androidTest/assets/fixtures/json_error_template.json @@ -1,3 +1,3 @@ { "error_message": "${error}" -} \ No newline at end of file +} diff --git a/requestmatcher/src/androidTest/assets/fixtures/json_template.json b/requestmatcher/src/androidTest/assets/fixtures/json_template.json index 29d915e..62bc0f0 100644 --- a/requestmatcher/src/androidTest/assets/fixtures/json_template.json +++ b/requestmatcher/src/androidTest/assets/fixtures/json_template.json @@ -1,3 +1,3 @@ { "current_date": "${current_date}" -} \ No newline at end of file +} diff --git a/requestmatcher/src/test/resources/fixtures/json_error_template.json b/requestmatcher/src/test/resources/fixtures/json_error_template.json index a74dbe1..32eac81 100644 --- a/requestmatcher/src/test/resources/fixtures/json_error_template.json +++ b/requestmatcher/src/test/resources/fixtures/json_error_template.json @@ -1,3 +1,3 @@ { "error_message": "${error}" -} \ No newline at end of file +} diff --git a/requestmatcher/src/test/resources/fixtures/json_template.json b/requestmatcher/src/test/resources/fixtures/json_template.json index 29d915e..62bc0f0 100644 --- a/requestmatcher/src/test/resources/fixtures/json_template.json +++ b/requestmatcher/src/test/resources/fixtures/json_template.json @@ -1,3 +1,3 @@ { "current_date": "${current_date}" -} \ No newline at end of file +} From f85925134d413418e2a8d35395738fc5ec4ca824 Mon Sep 17 00:00:00 2001 From: Matheus Cassiano Candido Date: Tue, 20 Mar 2018 19:29:16 -0300 Subject: [PATCH 6/6] Fixing method call in README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e488c9..cbf88c2 100644 --- a/README.md +++ b/README.md @@ -156,13 +156,14 @@ A valid JSON template is: And it can be used like this: -``` java +```java String timestamp = String.valueOf(new Date(778338900L).getTime()); server.addTemplate("json_template.json") .withValueForKey("current_date", timestamp) - .ifRequestMatches("/get_current_date"); + .ifRequestMatches() + .pathIs("/get_current_date"); // interact with server and make response assertions