Skip to content
forked from macdao/moscow

Moscow is a tool for testing provider's API using Moco's configuration file (contracts)

Notifications You must be signed in to change notification settings

uuau99999/moscow

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Moscow

Moco's friends

Moscow is a tool for testing provider's API using Moco's configuration file. It is highly influenced by Consumer-Driven Contracts pattern.

Moscow can turn Moco contracts into executable tests. You can also use Moscow as a TDD tool to write RESTful APIs.

Why Moco & Moscow

Moco uses JSON to describe API. With Moco, you can easily describe JSON-based RESTful APIs.

There are similar tools, such as RAML (with YAML) and API Blueprint (with Markdown). But they are not very friendly to JSON. Swagger is also a JSON-based tool, but it also uses similar schema as RAML and API Blueprint.

Moco uses example ("Contract") otherthan schema. You can find the reason here: It makes Contract Driven Development possible!

Moco and Moscow already contributed on my projects. Hope Moscow can help you too!

Usages

Installation

You can get Moscow by maven or gradle. To import by gradle:

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'com.github.macdao:moscow:0.1.0'
}

SNAPSHOT version:

repositories {
    mavenCentral()
    maven {
        url 'https://oss.sonatype.org/content/groups/public/'
    }
}

dependencies {
    testCompile 'com.github.macdao:moscow:0.1-SNAPSHOT'
}

If you are using Spring Boot (spring-boot-starter-web for more specific) that's all. But if you aren't using Spring Boot and don't want to depend on it, that's OK, Moscow can run without Spring Framework. The only thing you have to do is adding the OkHttp:

dependencies {
    testRuntime 'com.squareup.okhttp3:okhttp:3.1.2'
}

Basic Usages

  1. Create contract json file and save it into target folder (i.e. src/test/resources/contracts).
[
    {
        "description": "should_response_text_foo",
        "response": {
            "text": "foo"
        }
    }
]

Each contract is a test case, description is used to identify the contract. The description can follow TEST naming rules. Such as BDD style and User story style. I used $role_can/cannot_$do_something[_when_$sometime] style in a RESTful API project.

  1. Create an instance of ContractContainer in contracts directory:
private static final ContractContainer contractContainer = new ContractContainer(Paths.get("src/test/resources/contracts"));
  1. create an instance of ContractAssertion and call the assertContract method:
  @Test
  public void should_response_text_foo() throws Exception {
      new ContractAssertion(contractContainer.findContracts("should_response_text_foo"))
              .setPort(12306)
              .assertContract();
  }

The method ContractContainer.findContracts will return a contract list, which means you can assert multiple contracts with same description meanwhile.

assertContract will build request from contract, send it to server and compare the response with contract. It will compare existed status code, headers and body. Moscow only considers headers present in contracts and ignore the rest.

Path Matcher

Moscow uses path matcher to get created resource from Location header in RESTful APIs for 201 reponse.

"headers": {
    "Location": "http://{host}:{port}/bar/{bar-id}"
}

{bar-id} is the new generated ID. You can get the ID for future usage:

final String barId = new ContractAssertion(contractContainer.findContracts(name.getMethodName()))
                .setPort(12306)
                .assertContract()
                .get("bar-id");

{host} and {port} is special as it will be replaced by real host and port before assertion. Both of them can be used in response json body.

Moscow also support the ID appear in the contract response body:

"json": {
    "id": "{bar-id}"
}

Variable

There are 2 handy method variable and variables to define variable for you contract during the runtime. For example:

@Test
public void should_return_replaced_contract() throws Exception {
    new DefaultContractAssertion(contractContainer.findContracts(name.getMethodName()))
            .setPort(12306)
            .variable("host", "localhost")
            .variable("name", "juntao")
            .variable("email", "[email protected]")
            .setRestExecutor(new RestTemplateExecutor())
            .assertContract();
}

And your contract looks like this:

"response": {
  "json": {
    "author": {
      "name": "{name}",
      "email": "{email}"
    },
    "_links": {
      "self": "http://{host}:{port}/payload/1"
    }
  }
}

Before moscow actual do the comparation, it will replace all the variables, and then compare it with the real payload returned from the API.

Of course you can use variables to add all variables at once:

@Test
public void should_return_replaced_contract2() throws Exception {
    Map<String, String> context = new HashMap<>();

    context.put("name", "juntao");
    context.put("host", "localhost");
    context.put("port", "12306");
    context.put("email", "[email protected]");

    new DefaultContractAssertion(contractContainer.findContracts(name.getMethodName()))
            .variables(context)
            .setRestExecutor(new RestTemplateExecutor())
            .assertContract();
}

Customize the ContractAssertion

public class MyContractAssertion extends DefaultContractAssertion {

    public MyContractAssertion(List<Contract> contracts) {
        super(contracts);
    }

    @Override
    public void assertBody(RestResponse responseEntity, Contract contract) {
        String body = responseEntity.getBody();

        ContractResponse contractResponse = contract.getResponse();
        JsonConverter jsonConverter = JsonConverterFactory.getJsonConverter();

        List<String> items = JsonPath.read(body, "$.items");
        assert jsonConverter != null;

        String serializedContract = jsonConverter.serialize(contractResponse.getJson());
        List<String> items2 = JsonPath.read(serializedContract, "$.items");

        assertThat(items, is(items2));
    }
}

You can actually override all of those method:

public abstract class AbstractContractAssertion {
    public abstract void assertContract(RestResponse responseEntity, Contract contract);
    public abstract void assertStatusCode(RestResponse responseEntity, ContractResponse contractResponse);
    public abstract void assertHeaders(RestResponse responseEntity, ContractResponse contractResponse);
    public abstract void assertBody(RestResponse responseEntity, Contract contract);
}

Necessity Mode

Not all the response body is necessary. For example, Spring returns the followings for 401 response:

{
    "timestamp": 1455330165626,
    "status": 401,
    "error": "Unauthorized",
    "exception": "org.springframework.security.access.AccessDeniedException",
    "message": "Full authentication is required to access this resource",
    "path": "/api/users"
}

You may not need the timestamp, only message is necessary, so your contract would be:

"response": {
    "status": 401,
    "json": {
        "message": "Full authentication is required to access this resource"
    }
}

Moscow can support it by necessity mode:

@Test
public void request_text_bar4_should_response_foo() throws Exception {
    new ContractAssertion(contractContainer.findContracts(name.getMethodName()))
            .setPort(12306)
            .setNecessity(true)
            .assertContract();
}

Timeout

Inspired by Performance testing as a first-class citizen, I put execution time limitation in Moscow.

@Test(expected = RuntimeException.class)
public void request_text_bar5_should_response_timeout() throws Exception {
    new ContractAssertion(contractContainer.findContracts(name.getMethodName()))
            .setPort(12306)
            .setExecutionTimeout(100)
            .assertContract();
}

More Examples

https://github.com/macdao/moscow/tree/master/src/test

Best Practice

Group your json files

If there are many APIs in your project, it will be swarmed with json files. I prefer grouping APIs by URI into one file. The URI will exactly match the file path.

For example, given contract root directory is src/test/resources/contracts, contracts POST /api/users and GET /api/users should be put in src/test/resources/contracts/api/users.json, contracts GET /api/users/user-id-1 should be put in src/test/resources/contracts/users/user-id-1.json.

Static ContractContainer

ContractContainer instance can be reused to avoid duplcate.

TestName Rule

In JUnit, using TestName rule can get current test method name.

@Rule
public final TestName name = new TestName();

@Test
public void should_response_text_foo() throws Exception {
    new ContractAssertion(contractContainer.findContracts(name.getMethodName()))
            .setPort(12306)
            .assertContract();
}

Superclass

You can create ContractAssertion only once in superclass. Find examples here. It also works for contract names.

public class MyApiTest extends ApiTestBase {
    @Test
    public void request_param_should_response_text_bar4() throws Exception {
        assertContract();
    }
}

Spring Boot Integration

Here is a sample using Spring Boot. Spring's integration testing can auto start application in a servlet container so you don't need to worry about the application starting.

DB Migration

Because tests may change the data in database, you can re-migrate database before tests. For example, I use Flyway:

@Autowired
private Flyway flyway;

@Before
public void setUp() throws Exception {
    flyway.clean();
    flyway.migrate();
}

Supported Moco Features

Moscow use a subset of Moco contracts:

  • request
  • text (with method)
  • file (with method)
  • uri
  • queries
  • method (upper case)
  • headers
  • json (with method)
  • response
  • text
  • status
  • headers
  • json
  • file

Not Supported Moco Features

Because we need to build requests from Moco contracts, some matchers such as xpaths and json_paths is not supported.

  • request
  • version
  • cookies
  • forms
  • text.xml
  • text.json
  • file.xml
  • file.json
  • xpaths
  • json_paths
  • uri.match
  • uri.startsWith
  • uri.endsWith
  • uri.contain
  • exist
  • response
  • path_resource
  • version
  • proxy
  • cookies
  • attachment
  • latency
  • redirectTo
  • mount

Build

  1. start Moco for testing
./startMoco
  1. build
./gradlew clean build

Contribute

To disable the task signArchives, execute

$ gradle build -x signArchives

and to run all the tests, you should start moco server:

./startMoco

it will download the moco-standalone, and run the moco server in port 12306.

If you are using IntelliJ Idea as IDE,

$ gradle build idea -x signArchives

About

Moscow is a tool for testing provider's API using Moco's configuration file (contracts)

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 87.5%
  • Shell 12.5%