Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demonstrate how to mock test SeaORM by separating core implementation from the web API #890

Merged
merged 33 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f8af015
Move core implementations to a standalone crate
shpun817 Jul 19, 2022
784e376
Set up integration test skeleton in `core`
shpun817 Jul 19, 2022
83e662d
Demonstrate mock testing with query
shpun817 Jul 19, 2022
8946d79
Move Rocket api code to a standalone crate
shpun817 Jul 21, 2022
a77ce86
Add mock execution
shpun817 Jul 21, 2022
58bb1f8
Add MyDataMyConsent in COMMUNITY.md (#889)
itsAmitGoyani Jul 19, 2022
c2ee8f6
Update COMMUNITY.md
billy1624 Jul 19, 2022
d481989
[cli] bump sea-schema to 0.9.3 (SeaQL/sea-orm#876)
billy1624 Jul 20, 2022
5a1e1e1
Update CHNAGELOG PR links
billy1624 Jul 20, 2022
5c46162
0.9.1 CHANGELOG
billy1624 Jul 20, 2022
689af5f
Auto discover and run all issues & examples CI (#903)
billy1624 Jul 21, 2022
e6e8236
Compile prepare_mock_db() conditionally based on "mock" feature
shpun817 Jul 21, 2022
cfb83be
Update workflow job to run mock test if `core` folder exists
shpun817 Jul 21, 2022
ef1132c
Update Actix3 example
shpun817 Sep 7, 2022
68e5017
Merge branch 'master' into mock-test-demo
shpun817 Sep 7, 2022
7fcc8fa
Fix merge conflict human error
shpun817 Sep 7, 2022
0fea81e
Update usize used in paginate to u64 (PR#789)
shpun817 Sep 7, 2022
df4dee3
Update sea-orm version in the Rocket example to 0.10.0
shpun817 Sep 7, 2022
4e56b4c
Fix GitHub workflow to run mock test for core crates
shpun817 Sep 7, 2022
1a09e00
Increase the robustness of core crate check by verifying that the `co…
shpun817 Sep 8, 2022
e06e85c
Update Actix(4) example
shpun817 Sep 8, 2022
ffd34dc
Update Axum example
shpun817 Sep 8, 2022
d211f0b
Update GraphQL example
shpun817 Sep 8, 2022
4e179db
Update Jsonrpsee example
shpun817 Sep 9, 2022
3f9b04d
Update Poem example
shpun817 Sep 9, 2022
c96b69e
Update Tonic example
shpun817 Sep 9, 2022
3a58670
Cargo fmt
shpun817 Sep 9, 2022
e66f40b
Update Salvo example
shpun817 Sep 10, 2022
3c79b01
Update path of core/Cargo.toml in README.md
shpun817 Sep 10, 2022
2fbb964
Add mock test instruction in README.md
shpun817 Sep 10, 2022
ac78c71
Refactoring
billy1624 Sep 21, 2022
32169c0
Fix Rocket examples
billy1624 Sep 21, 2022
5698638
Merge pull request #3 from billy1624/pr/890
shpun817 Sep 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,15 @@ jobs:
args: >
--manifest-path ${{ matrix.path }}

- name: Run mock test if it is core crate
uses: actions-rs/cargo@v1
if: ${{ contains(matrix.path, 'core/Cargo.toml') }}
with:
command: test
args: >
--manifest-path ${{ matrix.path }}
--features mock

- name: check rustfmt
run: |
rustup override set nightly
Expand Down
27 changes: 2 additions & 25 deletions examples/actix3_example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,7 @@ edition = "2021"
publish = false

[workspace]
members = [".", "entity", "migration"]
members = [".", "api", "core", "entity", "migration"]

[dependencies]
actix-http = "2"
actix-web = "3"
actix-flash = "0.2"
actix-files = "0.5"
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
tera = "1.8.0"
dotenv = "0.15"
listenfd = "0.3.3"
serde = "1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
entity = { path = "entity" }
migration = { path = "migration" }

[dependencies.sea-orm]
path = "../../" # remove this line in your own project
version = "^0.10.0" # sea-orm version
features = [
"debug-print",
"runtime-async-std-native-tls",
"sqlx-mysql",
# "sqlx-postgres",
# "sqlx-sqlite",
]
actix3-example-api = { path = "api" }
9 changes: 8 additions & 1 deletion examples/actix3_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database

1. Turn on the appropriate database feature for your chosen db in `Cargo.toml` (the `"sqlx-mysql",` line)
1. Turn on the appropriate database feature for your chosen db in `core/Cargo.toml` (the `"sqlx-mysql",` line)

1. Execute `cargo run` to start the server

Expand All @@ -18,3 +18,10 @@ Run server with auto-reloading:
cargo install systemfd
systemfd --no-pid -s http::8000 -- cargo watch -x run
```

Run mock test on the core logic crate:

```bash
cd core
cargo test --features mock
```
22 changes: 22 additions & 0 deletions examples/actix3_example/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "actix3-example-api"
version = "0.1.0"
authors = ["Sam Samai <[email protected]>"]
edition = "2021"
publish = false

[dependencies]
actix3-example-core = { path = "../core" }
actix-http = "2"
actix-web = "3"
actix-flash = "0.2"
actix-files = "0.5"
futures = { version = "^0.3" }
futures-util = { version = "^0.3" }
tera = "1.8.0"
dotenv = "0.15"
listenfd = "0.3.3"
serde = "1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
entity = { path = "../entity" }
migration = { path = "../migration" }
219 changes: 219 additions & 0 deletions examples/actix3_example/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use actix3_example_core::{
sea_orm::{Database, DatabaseConnection},
Mutation, Query,
};
use actix_files as fs;
use actix_web::{
error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Result,
};

use entity::post;
use listenfd::ListenFd;
use migration::{Migrator, MigratorTrait};
use serde::{Deserialize, Serialize};
use std::env;
use tera::Tera;

const DEFAULT_POSTS_PER_PAGE: u64 = 5;

#[derive(Debug, Clone)]
struct AppState {
templates: tera::Tera,
conn: DatabaseConnection,
}
#[derive(Debug, Deserialize)]
pub struct Params {
page: Option<u64>,
posts_per_page: Option<u64>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct FlashData {
kind: String,
message: String,
}

#[get("/")]
async fn list(
req: HttpRequest,
data: web::Data<AppState>,
opt_flash: Option<actix_flash::Message<FlashData>>,
) -> Result<HttpResponse, Error> {
let template = &data.templates;
let conn = &data.conn;

// get params
let params = web::Query::<Params>::from_query(req.query_string()).unwrap();

let page = params.page.unwrap_or(1);
let posts_per_page = params.posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE);

let (posts, num_pages) = Query::find_posts_in_page(conn, page, posts_per_page)
.await
.expect("Cannot find posts in page");

let mut ctx = tera::Context::new();
ctx.insert("posts", &posts);
ctx.insert("page", &page);
ctx.insert("posts_per_page", &posts_per_page);
ctx.insert("num_pages", &num_pages);

if let Some(flash) = opt_flash {
let flash_inner = flash.into_inner();
ctx.insert("flash", &flash_inner);
}

let body = template
.render("index.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[get("/new")]
async fn new(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let template = &data.templates;
let ctx = tera::Context::new();
let body = template
.render("new.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[post("/")]
async fn create(
data: web::Data<AppState>,
post_form: web::Form<post::Model>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;

let form = post_form.into_inner();

Mutation::create_post(conn, form)
.await
.expect("could not insert post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully added.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[get("/{id}")]
async fn edit(data: web::Data<AppState>, id: web::Path<i32>) -> Result<HttpResponse, Error> {
let conn = &data.conn;
let template = &data.templates;
let id = id.into_inner();

let post: post::Model = Query::find_post_by_id(conn, id)
.await
.expect("could not find post")
.unwrap_or_else(|| panic!("could not find post with id {}", id));

let mut ctx = tera::Context::new();
ctx.insert("post", &post);

let body = template
.render("edit.html.tera", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
Ok(HttpResponse::Ok().content_type("text/html").body(body))
}

#[post("/{id}")]
async fn update(
data: web::Data<AppState>,
id: web::Path<i32>,
post_form: web::Form<post::Model>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;
let form = post_form.into_inner();
let id = id.into_inner();

Mutation::update_post_by_id(conn, id, form)
.await
.expect("could not edit post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully updated.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[post("/delete/{id}")]
async fn delete(
data: web::Data<AppState>,
id: web::Path<i32>,
) -> actix_flash::Response<HttpResponse, FlashData> {
let conn = &data.conn;
let id = id.into_inner();

Mutation::delete_post(conn, id)
.await
.expect("could not delete post");

let flash = FlashData {
kind: "success".to_owned(),
message: "Post successfully deleted.".to_owned(),
};

actix_flash::Response::with_redirect(flash, "/")
}

#[actix_web::main]
async fn start() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
tracing_subscriber::fmt::init();

// get env vars
dotenv::dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
let host = env::var("HOST").expect("HOST is not set in .env file");
let port = env::var("PORT").expect("PORT is not set in .env file");
let server_url = format!("{}:{}", host, port);

// create post table if not exists
let conn = Database::connect(&db_url).await.unwrap();
Migrator::up(&conn, None).await.unwrap();
let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
let state = AppState { templates, conn };

let mut listenfd = ListenFd::from_env();
let mut server = HttpServer::new(move || {
App::new()
.data(state.clone())
.wrap(middleware::Logger::default()) // enable logger
.wrap(actix_flash::Flash::default())
.configure(init)
.service(fs::Files::new("/static", "./api/static").show_files_listing())
});

server = match listenfd.take_tcp_listener(0)? {
Some(listener) => server.listen(listener)?,
None => server.bind(&server_url)?,
};

println!("Starting server at {}", server_url);
server.run().await?;

Ok(())
}

fn init(cfg: &mut web::ServiceConfig) {
cfg.service(list);
cfg.service(new);
cfg.service(create);
cfg.service(edit);
cfg.service(update);
cfg.service(delete);
}

pub fn main() {
let result = start();

if let Some(err) = result.err() {
println!("Error: {}", err)
}
}
30 changes: 30 additions & 0 deletions examples/actix3_example/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "actix3-example-core"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
entity = { path = "../entity" }

[dependencies.sea-orm]
path = "../../../" # remove this line in your own project
version = "^0.10.0" # sea-orm version
features = [
"debug-print",
"runtime-async-std-native-tls",
"sqlx-mysql",
# "sqlx-postgres",
# "sqlx-sqlite",
]

[dev-dependencies]
tokio = { version = "1.20.0", features = ["macros", "rt"] }

[features]
mock = ["sea-orm/mock"]

[[test]]
name = "mock"
required-features = ["mock"]
7 changes: 7 additions & 0 deletions examples/actix3_example/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod mutation;
mod query;

pub use mutation::*;
pub use query::*;

pub use sea_orm;
Loading