A simple and powerful way for making programatic assertions in your fake API
A simple and powerful way for making assertions in your mocked API.
To properly test an Android application we must isolate all the external dependencies that we can’t control. Normally, in a client/server application, this boils down to the API calls.
There are several approaches to mocking the server interaction:
Retrofit
you can use Retrofit-mock
which gives you easier ways to set up your mocking implementation.All of the above makes testing APIs possible though not highly configurable on a per test basis. There is another (and probably many others) approach:
This approach is nice though may generate lots of code in your tests to setup proper request assertions. This library tries to simplify that and add some other automatic detection of wrong doings in tests setup.
The library is available in JCenter repositories. To use it, just declare it in your app’s build.gradle:
dependencies {
// local tests
testCompile "br.com.concretesolutions:requestmatcher:$latestVersion"
// instrumented tests
androidTestCompile "br.com.concretesolutions:requestmatcher:$latestVersion"
}
This library depends on the following libraries:
So, ensure those libraries are also in your dependencies. For example:
dependencies {
// local tests
testCompile "br.com.concretesolutions:requestmatcher:$latestVersion"
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-all:1.3'
testCompile "com.squareup.okhttp3:mockwebserver:3.4.1"
testCompile 'com.jayway.jsonpath:json-path-assert:2.2.0' // optional
// instrumented tests
androidTestCompile "br.com.concretesolutions:requestmatcher:$latestVersion"
androidTestCompile "com.squareup.okhttp3:mockwebserver:3.4.1"
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.2" // this already has hamcrest
androidTestCompile "com.android.support.test:runner:0.5" // this already has junit
androidTestCompile 'com.jayway.jsonpath:json-path-assert:2.2.0' // optional
}
The core API of this library is centered around the class RequestMatcherRule
. This is a wrapping rule around Square’s MockWebServer
. A basic local test can be setup like:
public class LocalTest {
@Rule
public final RequestMatcherRule serverRule = new LocalTestRequestMatcherRule();
@Before
public void setUp() {
// Setup your application to point to this rootUrl
final String rootUrl = serverRule.url("/").toString();
// do setup
}
@Test
public void canMakeRequestAssertions() {
serverRule.addFixture(200, "body.json")
.ifRequestMatches()
.pathIs("/somepath")
.hasEmptyBody()
.methodIs(HttpMethod.GET);
// make interaction with the server
}
}
In this example, several things are checked:
rootUrl + "/somepath"
or else it will fail.We think this declarative way of making assertions on requests will make tests more consistent with expected behaviour.
MockResponse
sTo add a MockResponse
all you have to do is call one of the addResponse
methods from the server rule.
serverRule.addResponse(new MockResponse().setResponseCode(500));
To add a fixture all you have to do is call one of the addFixture
methods in the RequestMatcherRule
. That means you can save your mocks in a folder and load them up while you are mocking the API. Example:
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.
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.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:
// Local Test
@Rule
public final RequestMatcherRule server = new LocalTestRequestMatcherRule();
// or
// Instrumented Test
@Rule
public final RequestMatcherRule server = new InstrumentedTestRequestMatcherRule();
The difference is that when we run an InstrumentedTest, we must pass the instrumentation context (and NOT the target context).
More on the difference between each kind of test here
RequestMatcherRule
It is possible to pass some parameters to the server rule’s constructor:
MockWebServer
server: an instance of the MockWebServer to use instead of a default new one.String
fixturesRootFolder: the name of the folder in the corresponding context. Defaults to ‘fixtures’.When an assertion fails, it throws a RequestAssertionException
. Of course, this happens in the server thread and so, if we throw an exception from there the client will hang and most likely receive a timeout. This would make tests last too long and consequently the test suite. To avoid this, the assertion is buffered and the response is delivered as if it were disconnected. The response is like the snippet below:
new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
The RequestMatcherRule
provides a DSL for matching against requests. You can and should provide matchers against each part of a request. See the base RequestMatchersGroup
for all possible matching.
server.addFixture(200, "body.json")
.ifRequestMatches() // this is the entry point to configure matching
.pathIs("/post") // path must be "/post"
.headersMatches(hasEntry(any(String.class), is("value"))) // some header must contain value "value"
.methodIs(HttpMethod.PUT) // method must be PUT
.bodyMatches(containsString("\"property\": \"value\"")); // body must contain the string passed
RequestMatcher
The library is flexible enough for customizing the RequestMatcherGroup
implementation you want to use. To do that, use the method addResponse(MockResponse response, T matcher)
, addFixture(String path, T matcher)
or addFixture(int statusCode, String fixturePath, T matcher)
where matcher
is an instance of any class that extends RequestMatchersGroup
.
With that you can provide your own assertions, for example, you can create assertions according to some custom protocol. This is more useful for those not following strict RESTful architectures.
Example:
CustomMatcher matcher = server.addResponse(new MockResponse().setBody("Some body"), new CustomMatcher()).ifRequestMatches();
For more examples, please check the tests in the library module and the sample module.
This project is available under Apache Public License version 2.0. See LICENSE.