From 99b615d01dd098d1b7bdfa31c6e23ed81eb8f8da Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Sat, 13 Jul 2024 21:16:46 +0530 Subject: [PATCH] feat: test add store service with cqrs_es infra --- .../application/services/add_store_service.rs | 34 ++++++++++-- src/inventory/domain/store_aggregate.rs | 54 +++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/inventory/application/services/add_store_service.rs b/src/inventory/application/services/add_store_service.rs index d782d07..ad49d02 100644 --- a/src/inventory/application/services/add_store_service.rs +++ b/src/inventory/application/services/add_store_service.rs @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later +use std::sync::Arc; use derive_builder::Builder; -use uuid::Uuid; +use mockall::predicate::*; +use mockall::*; use super::errors::*; use crate::inventory::{ @@ -16,12 +18,13 @@ use crate::inventory::{ }; use crate::utils::uuid::*; +#[automock] #[async_trait::async_trait] pub trait AddStoreUseCase: Send + Sync { async fn add_store(&self, cmd: AddStoreCommand) -> InventoryResult; } -pub type AddStoreServiceObj = std::sync::Arc; +pub type AddStoreServiceObj = Arc; #[derive(Clone, Builder)] pub struct AddStoreService { @@ -66,12 +69,37 @@ impl AddStoreUseCase for AddStoreService { } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use crate::tests::bdd::*; use crate::utils::uuid::tests::*; + pub fn mock_add_store_service( + times: Option, + 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] async fn test_service_store_id_doesnt_exist() { let name = "foo"; diff --git a/src/inventory/domain/store_aggregate.rs b/src/inventory/domain/store_aggregate.rs index 50b8f6b..9a68280 100644 --- a/src/inventory/domain/store_aggregate.rs +++ b/src/inventory/domain/store_aggregate.rs @@ -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; + + #[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]); + } +}