feat: test add store service with cqrs_es infra #27
2 changed files with 85 additions and 3 deletions
|
@ -1,9 +1,11 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
use uuid::Uuid;
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
|
||||||
use super::errors::*;
|
use super::errors::*;
|
||||||
use crate::inventory::{
|
use crate::inventory::{
|
||||||
|
@ -16,12 +18,13 @@ use crate::inventory::{
|
||||||
};
|
};
|
||||||
use crate::utils::uuid::*;
|
use crate::utils::uuid::*;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AddStoreUseCase: Send + Sync {
|
pub trait AddStoreUseCase: Send + Sync {
|
||||||
async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult<StoreAddedEvent>;
|
async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult<StoreAddedEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AddStoreServiceObj = std::sync::Arc<dyn AddStoreUseCase>;
|
pub type AddStoreServiceObj = Arc<dyn AddStoreUseCase>;
|
||||||
|
|
||||||
#[derive(Clone, Builder)]
|
#[derive(Clone, Builder)]
|
||||||
pub struct AddStoreService {
|
pub struct AddStoreService {
|
||||||
|
@ -66,12 +69,37 @@ impl AddStoreUseCase for AddStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::tests::bdd::*;
|
use crate::tests::bdd::*;
|
||||||
use crate::utils::uuid::tests::*;
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
|
pub fn mock_add_store_service(
|
||||||
|
times: Option<usize>,
|
||||||
|
cmd: AddStoreCommand,
|
||||||
|
) -> AddStoreServiceObj {
|
||||||
|
let mut m = MockAddStoreUseCase::new();
|
||||||
|
|
||||||
|
let res = StoreAddedEventBuilder::default()
|
||||||
|
.name(cmd.name().into())
|
||||||
|
.address(cmd.address().as_ref().map(|s| s.to_string()))
|
||||||
|
.owner(cmd.owner().into())
|
||||||
|
.store_id(UUID.clone())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_add_store()
|
||||||
|
.times(times)
|
||||||
|
.returning(move |_| Ok(res.clone()));
|
||||||
|
} else {
|
||||||
|
m.expect_add_store().returning(move |_| Ok(res.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_service_store_id_doesnt_exist() {
|
async fn test_service_store_id_doesnt_exist() {
|
||||||
let name = "foo";
|
let name = "foo";
|
||||||
|
|
|
@ -72,3 +72,57 @@ impl Aggregate for Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The aggregate tests are the most important part of a CQRS system.
|
||||||
|
// The simplicity and flexibility of these tests are a good part of what
|
||||||
|
// makes an event sourced system so friendly to changing business requirements.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod aggregate_tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use cqrs_es::test::TestFramework;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::inventory::{
|
||||||
|
application::services::{add_store_service::tests::*, *},
|
||||||
|
domain::{
|
||||||
|
add_store_command::*, commands::InventoryCommand, events::InventoryEvent,
|
||||||
|
store_added_event::*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::tests::bdd::*;
|
||||||
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
|
// A test framework that will apply our events and command
|
||||||
|
// and verify that the logic works as expected.
|
||||||
|
type StoreTestFramework = TestFramework<Store>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_store() {
|
||||||
|
let name = "store_name";
|
||||||
|
let address = Some("store_address".to_string());
|
||||||
|
let owner = "store_owner";
|
||||||
|
let store_id = UUID;
|
||||||
|
|
||||||
|
let expected = StoreAddedEventBuilder::default()
|
||||||
|
.name(name.into())
|
||||||
|
.address(address.clone())
|
||||||
|
.store_id(store_id.clone())
|
||||||
|
.owner(owner.into())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let expected = InventoryEvent::StoreAdded(expected);
|
||||||
|
|
||||||
|
let cmd = AddStoreCommand::new(name.into(), address.clone(), owner.into()).unwrap();
|
||||||
|
|
||||||
|
let mut services = MockInventoryServicesInterface::new();
|
||||||
|
services
|
||||||
|
.expect_add_store()
|
||||||
|
.times(IS_CALLED_ONLY_ONCE.unwrap())
|
||||||
|
.return_const(mock_add_store_service(IS_CALLED_ONLY_ONCE, cmd.clone()));
|
||||||
|
|
||||||
|
StoreTestFramework::with(Arc::new(services))
|
||||||
|
.given_no_previous_events()
|
||||||
|
.when(InventoryCommand::AddStore(cmd))
|
||||||
|
.then_expect_events(vec![expected]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue