Skip to content

Commit

Permalink
file based integration tests (#5067)
Browse files Browse the repository at this point in the history
This introduces a new way to write integration tests, by using JSON files to describe the test plan. This has the benefit of not requiring any recompilation, at the cost of a slightly higher test time becomes it needs to start an entire router
  • Loading branch information
Geal committed May 16, 2024
1 parent f41065e commit 10a76bd
Show file tree
Hide file tree
Showing 13 changed files with 861 additions and 5 deletions.
26 changes: 24 additions & 2 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ dependencies = [
"jsonwebtoken",
"lazy_static",
"libc",
"libtest-mimic",
"linkme",
"lru",
"maplit",
Expand Down Expand Up @@ -2515,6 +2516,15 @@ dependencies = [
"windows-sys 0.52.0",
]

[[package]]
name = "escape8259"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee"
dependencies = [
"rustversion",
]

[[package]]
name = "event-listener"
version = "2.5.3"
Expand Down Expand Up @@ -3900,6 +3910,18 @@ dependencies = [
"vcpkg",
]

[[package]]
name = "libtest-mimic"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fefdf21230d6143476a28adbee3d930e2b68a3d56443c777cae3fe9340eebff9"
dependencies = [
"clap",
"escape8259",
"termcolor",
"threadpool",
]

[[package]]
name = "libz-ng-sys"
version = "1.1.12"
Expand Down Expand Up @@ -7729,9 +7751,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"

[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
Expand Down
6 changes: 6 additions & 0 deletions apollo-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ tracing-opentelemetry = "0.21.0"
tracing-test = "0.2.4"
walkdir = "2.4.0"
wiremock = "0.5.22"
libtest-mimic = "0.7.2"

[target.'cfg(target_os = "linux")'.dev-dependencies]
rstack = { version = "0.3.3", features = ["dw"], default-features = false }
Expand All @@ -353,6 +354,11 @@ serde_json.workspace = true
name = "integration_tests"
path = "tests/integration_tests.rs"

[[test]]
name = "samples"
path = "tests/samples_tests.rs"
harness = false

[[bench]]
name = "huge_requests"
harness = false
Expand Down
14 changes: 11 additions & 3 deletions apollo-router/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ use wiremock::ResponseTemplate;
pub struct IntegrationTest {
router: Option<Child>,
test_config_location: PathBuf,
test_schema_location: PathBuf,
router_location: PathBuf,
stdio_tx: tokio::sync::mpsc::Sender<String>,
stdio_rx: tokio::sync::mpsc::Receiver<String>,
collect_stdio: Option<(tokio::sync::oneshot::Sender<String>, regex::Regex)>,
supergraph: PathBuf,
_subgraphs: wiremock::MockServer,
telemetry: Telemetry,

Expand Down Expand Up @@ -317,10 +317,13 @@ impl IntegrationTest {
.await;

let mut test_config_location = std::env::temp_dir();
let mut test_schema_location = test_config_location.clone();
let location = format!("apollo-router-test-{}.yaml", Uuid::new_v4());
test_config_location.push(location);
test_schema_location.push(format!("apollo-router-test-{}.graphql", Uuid::new_v4()));

fs::write(&test_config_location, &config_str).expect("could not write config");
fs::copy(&supergraph, &test_schema_location).expect("could not write schema");

let (stdio_tx, stdio_rx) = tokio::sync::mpsc::channel(2000);
let collect_stdio = collect_stdio.map(|sender| {
Expand All @@ -332,10 +335,10 @@ impl IntegrationTest {
router: None,
router_location: Self::router_location(),
test_config_location,
test_schema_location,
stdio_tx,
stdio_rx,
collect_stdio,
supergraph,
_subgraphs: subgraphs,
_subgraph_overrides: subgraph_overrides,
bind_address: Default::default(),
Expand Down Expand Up @@ -383,7 +386,7 @@ impl IntegrationTest {
"--config",
&self.test_config_location.to_string_lossy(),
"--supergraph",
&self.supergraph.to_string_lossy(),
&self.test_schema_location.to_string_lossy(),
"--log",
"error,apollo_router=info",
])
Expand Down Expand Up @@ -477,6 +480,11 @@ impl IntegrationTest {
.expect("must be able to write config");
}

#[allow(dead_code)]
pub async fn update_schema(&self, supergraph_path: &PathBuf) {
fs::copy(supergraph_path, &self.test_schema_location).expect("could not write schema");
}

#[allow(dead_code)]
pub fn execute_default_query(
&self,
Expand Down
119 changes: 119 additions & 0 deletions apollo-router/tests/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# File based integration tests

This folder contains a serie of Router integration tests that can be defined entirely through a JSON file. Thos tests are able to start and stop a router, reload its schema or configiration, make requests and check the expected response. While we can make similar tests from inside the Router's code, these tests here are faster to write and modify because they do not require recompilations of the Router, at the cost of a slightly higher runtime cost.

## How to write a test

One test is recognized as a folder containing a `plan.json` file. Any number of subfolders is accepted, and the test name will be the path to the test folder. If the folder contains a `README.md` file, it will be added to the captured output of the test, and displayed if the test failed.

The `plan.json` file contains a top level JSON object with an `actions` field, containing an array of possible actions, that will be executed one by one:

```json
{
"actions": [
{
"type": "Start",
"schema_path": "./supergraph.graphql",
"configuration_path": "./configuration.yaml",
"subgraphs": {}
},
{
"type": "Request",
"request": {
"query": "{ me { name } }"
},
"expected_response": {
"data":{
"me":{
"name":"Ada Lovelace"
}
}
}
},
{
"type": "Stop"
}
]
}
```

If any of those actions fails, the test will stop immediately.

## Possible actions

### Start

```json
{
"type": "Start",
"schema_path": "./supergraph.graphql",
"configuration_path": "./configuration.yaml",
"subgraphs": {
"accounts": {
"requests": [
{
"request": {"query":"{me{name}}"},
"response": {"data": { "me": { "name": "test" } } }
},
{
"request": {"query":"{me{nom:name}}"},
"response": {"data": { "me": { "nom": "test" } } }
}
]
}
}
}
```

the `schema_path` and `configuration_path` field are relative to the test's folder. The `subgraph` field can contain mocked requests and responses for each subgraph. If the Router fails to load with this schema and configuration, then this action will fail the test.

## Reload configuration

Reloads the router with a new configuration file. If the Router fails to load the new configuration, then this action will fail the test.

```json
{
"type": "ReloadConfiguration",
"configuration_path": "./configuration.yaml"
}
```

## Reload schema

Reloads the router with a new schema file. If the Router fails to load the new configuration, then this action will fail the test.

```json
{
"type": "ReloadSchema",
"schema_path": "./supergraph.graphql"
}
```

## Request

Sends a request to the Router, and verifies that the response body matches the expected response. If it does not match or returned any HTTP error, then this action will fail the test.
```json
{
"type": "Request",
"request": {
"query": "{ me { name } }"
},
"expected_response": {
"data":{
"me":{
"name":"Ada Lovelace"
}
}
}
}
```

### Stop

Stops the Router. If the Router does not stop correctly, then this action will fail the test.

```json
{
"type": "Stop"
}
```
3 changes: 3 additions & 0 deletions apollo-router/tests/samples/basic/query1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is an example test

This file adds some context that will be displayed on test failure
4 changes: 4 additions & 0 deletions apollo-router/tests/samples/basic/query1/configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
override_subgraph_url:
products: http://localhost:4005
include_subgraph_errors:
all: true
52 changes: 52 additions & 0 deletions apollo-router/tests/samples/basic/query1/plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"actions": [
{
"type": "Start",
"schema_path": "./supergraph.graphql",
"configuration_path": "./configuration.yaml",
"subgraphs": {
"accounts": {
"requests": [
{
"request": {"query":"{me{name}}"},
"response": {"data": { "me": { "name": "test" } } }
},
{
"request": {"query":"{me{nom:name}}"},
"response": {"data": { "me": { "nom": "test" } } }
}
]
}
}
},
{
"type": "Request",
"request": {
"query": "{ me { name } }"
},
"expected_response": {
"data":{
"me":{
"name":"test"
}
}
}
},
{
"type": "Request",
"request": {
"query": "{ me { nom: name } }"
},
"expected_response": {
"data": {
"me": {
"nom": "test"
}
}
}
},
{
"type": "Stop"
}
]
}
Loading

0 comments on commit 10a76bd

Please sign in to comment.