GithubHelp home page GithubHelp logo

concretesolutions / requestmatcher Goto Github PK

View Code? Open in Web Editor NEW
44.0 14.0 11.0 273 KB

A simple and powerful way for making programatic assertions in your fake API

License: Apache License 2.0

Java 100.00%
mock-server requestmatcher fixtures mockwebserver making-assertions android-library android-tests

requestmatcher's Introduction

RequestMatcher

Build Status

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:

  • Dependency Injection: use a test version of your server interaction component.
  • Evil API: have a test implementation of your API contracts (implement the interface for tests).
  • Use specific mocking libraries: if you use 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:

  • Use a mock web server: a real server responding to requests that you set up on your test to the expected behaviour.

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.

Download

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
}

Usage

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:

  • The test MUST make exclusively ONE request to the mock server or else it will fail.
  • The request must be a GET or else it will fail.
  • The request must not contain a body or else it will fail.
  • The request must target path 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.

Adding MockResponses

To 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));

Adding fixtures

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.

  • 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.

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

Configuring the 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'.

RequestAssertionException

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);

Request Matching

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.

Examples

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

Custom 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();

Other examples

For more examples, please check the tests in the library module and the sample module.

LICENSE

This project is available under Apache Public License version 2.0. See LICENSE.

requestmatcher's People

Contributors

cleemansen avatar cs-bruno-silva avatar cs-rafael-toledo avatar cs-victor-nascimento avatar rafaeltoledo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

requestmatcher's Issues

Add fixture processing order bug?

Hi, is there any assurance that the enqueue order is the processing order?
I mean the code below works "some times"...

  @Test
  fun shouldFailDueRequestOrder() {
      val url = server.url("/").toString()
      val request = Request.Builder()
              .url(url + "get")
              .build()

      val client = OkHttpClient
              .Builder()
              .build()

      server.addResponse(MockResponse()
              .setResponseCode(HttpStatus.HTTP_UNAVAILABLE))
              .ifRequestMatches()
              .pathIs("/get")

      server.addFixture(200, "test.json")
              .ifRequestMatches()
              .pathIs("/get")

      assertFalse(client.newCall(request).execute().isSuccessful)

      assertTrue(client.newCall(request).execute().isSuccessful)
  }

To make it work all times we need to move the addFixture to after the first assert.

    @Test
    fun shouldFailDueRequestOrder() {
        val url = server.url("/").toString()
        val request = Request.Builder()
                .url(url + "get")
                .build()

        val client = OkHttpClient
                .Builder()
                .build()

        server.addResponse(MockResponse()
                .setResponseCode(HttpStatus.HTTP_UNAVAILABLE))
                .ifRequestMatches()
                .pathIs("/get")

        assertFalse(client.newCall(request).execute().isSuccessful)

        server.addFixture(200, "test.json")
                .ifRequestMatches()
                .pathIs("/get")

        assertTrue(client.newCall(request).execute().isSuccessful)
    }

is this the expected behaviour? It would be helpful to be able to declare the expected requests in the beginning of each test so we split the setup from the test execution.

Remove use of Android classes

The library is using some Android classes, like TextUtils.
That way I can't use requestmatcher in unit tests that does not depends on Android classes.

Creating dynamic JSONs

There's some specific use cases where you need to enqueue a request with dynamic data (e.g., time/date based tests). Would be great if the library allow to enqueue a dynamic JSON (through String, JsonElement or some other type).

I will think about it and open a PR.

Open for discussion.

Refactor tests to have a common base

Currently we are duplicating a lot of tests because this project supports either Local or Instrumented tests. We should use a configuration much like this one.

This will ease verifying common cases without sacrificing much.

Recorded Request Body must be read multiple times

Hi all,

first of all thanks for that very helpful tool!

I've noticed that there is an issue when you need to get the body of a recorded request multiple times.

This happens when there are multiple requests in the Set which are equal in Request-Method, Request-Path, Request-Query, Request-Headers but with different Request-Body.
In that situation it is possible that the "wrong" matcher is in front of the correct matcher. Because the "wrong" matcher takes the body of the taken request this body is not any more available for future asserts.

The second issue is that I had a hard time to figure out which matcher did not match because of what reason.

For details please see my pull request...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.