/mongodb-service-template

A cargo generate template for a mongodb service using graphql.

Primary LanguageRust

Sample Service

Note

This is a project template using cargo generate. The idea is to create a new service from scratch using this as a template. This includes a sample model and embedded object model as well as a bunch of tests. It requires using the latest version of cargo generate. So to install that do:

cargo install --git https://github.com/ashleygwilliams/cargo-generate

Then to create a new project:

cargo generate --git https://github.com/briandeboer/mongodb-service-template

Answer the prompts and voila! you have a new project.

Getting Started

  • Install Rust
  • Run cargo run to build and run service

Install Docker and Docker Compose (Optional)

Start Mongo

You can skip this step if you prefer to run a local Mongo instance.

Using docker-compose

docker-compose up -d mongo

In the future you can run docker start mongo to relaunch the service.

VSCode

Plugins

  • Better TOML
  • Native Debug
  • Rust
  • rust-analyzer

Debugging

Run debug (F5) to create a launch.json file, make sure the value of target is pointed to your built executable, usually ./target/rust-deps/debug/{{project-name}} unless you changed $CARGO_TARGET_DIR

Graphiql

Docker

You can run the service with docker-compose. It currently doesn't take into consideration the login-service, but that is something to look into the best way to accomplish.

Build

docker build -t {{project-name}}:latest .

Run

docker-compose up -d

Tests

In order to run tests against graphql you will need to have a local runnning mongo server on port 27017. You can use docker-compose or whatever method you want to have that. It will automatically create a new database just for tests named {{project-name}}-test. In order for your test to leverage the database you should configure your test app with the load-filled-database. That will read all mock data from the tests/mock folder and insert them into the database. Here's an example...

use crate::utils;

...

let mut app = test::init_service(
    App::new()
        .configure(utils::load_filled_database)
        .configure(app_routes),
).await;

For more information, look at the schema/query/users.rs tests. For most tests, using test snapshots will work well. For more information on snapshot testing see here. The tests rely on the insta crate. First install insta follow the instructions on their site:

cargo install cargo-insta

To run the tests first make sure that you have mongo running on local port 27084 or use docker-compose:

docker-compose up -d mongo
./test

If any of your tests fail or have new snapshots, you can review them with:

cargo insta review

Time manipulation

For some snapshot tests you'll need to lock SystemTime to a fixed number to prevent things like date_modified or date_created updates to differ between snapshots. Because mongodb-base-service automatically updates the objects with those times, it has been updated to allow for mocking time. This already happens inside the load_filled_database function. But if you need to override it or fill the database with your own distinct mock data, you can set the time to a specific number:

// fix time to Jan 1, 2020 so that snapshots always have the same date_created, etc...
mock_time::set_mock_time(SystemTime::UNIX_EPOCH + Duration::from_millis(1577836800000));

Or if you need to increase the time to verify that the date_modified has changed:

// increase time by 10 seconds
mock_time::increase_mock_time(10000);

If you want to reset the time to normal SystemTime, you can use:

// revert to normal SystemTime
mock_time::clear_mock_time();

Notes on multi-threading

It's important to understand that parallel tests which all write to a single database or manipulate SystemTime can cause issues and break tests. For that reason to ensure that the tests all run independently you should use:

cargo test --jobs=1 -- --test-threads=1

When you run them with concurrenncy disabled that does mean that insta will fail. In order to resolve that be sure that all tests include a snapshot name

assert_snapshot!("test_snapshot_name", format!("{:?}", resp));