5 releases
| 0.1.8 | Jul 23, 2025 |
|---|---|
| 0.1.7 | Jul 22, 2025 |
| 0.1.5 | Jul 21, 2025 |
| 0.1.4 | Jul 21, 2025 |
| 0.1.3 | Jul 9, 2025 |
#35 in #event-sourcing-cqrs
21 downloads per month
Used in union_square
1MB
24K
SLoC
eventcore-memory
In-memory event store adapter for EventCore - perfect for testing and development.
Features
- Zero setup - No database required
- Thread-safe - Safe for concurrent testing
- Fast - No I/O overhead
- Deterministic - Consistent test results
- Full API compatibility - Drop-in replacement
Installation
[dev-dependencies]
eventcore-memory = "0.1"
Usage in Tests
use eventcore_memory::MemoryEventStore;
use eventcore::{CommandExecutor, testing::*};
#[tokio::test]
async fn test_my_command() {
// Create store - no setup needed!
let store = MemoryEventStore::new();
let executor = CommandExecutor::new(store);
// Test your commands
let result = executor.execute(MyCommand { ... }).await;
assert!(result.is_ok());
}
Test Patterns
Given-When-Then Testing
use eventcore::testing::CommandTestHarness;
#[tokio::test]
async fn transfer_should_move_money() {
CommandTestHarness::new()
.given_events(vec![
AccountOpened { id: "alice", balance: Money::new(1000) },
AccountOpened { id: "bob", balance: Money::new(0) },
])
.when(TransferMoney {
from: "alice",
to: "bob",
amount: Money::new(100)
})
.then_expect_events(vec![
MoneyWithdrawn { account: "alice", amount: Money::new(100) },
MoneyDeposited { account: "bob", amount: Money::new(100) },
])
.run()
.await
.unwrap();
}
Testing Concurrency
#[tokio::test]
async fn concurrent_transfers_should_not_overdraw() {
let store = MemoryEventStore::new();
let executor = CommandExecutor::new(store);
// Setup account
executor.execute(OpenAccount {
id: "alice",
initial: Money::new(100)
}).await.unwrap();
// Try concurrent transfers
let transfer1 = executor.execute(TransferMoney {
from: "alice", to: "bob", amount: Money::new(60)
});
let transfer2 = executor.execute(TransferMoney {
from: "alice", to: "charlie", amount: Money::new(60)
});
let (result1, result2) = tokio::join!(transfer1, transfer2);
// One should succeed, one should fail
assert!(result1.is_ok() ^ result2.is_ok());
}
Testing Projections
#[tokio::test]
async fn projection_should_track_balances() {
let store = MemoryEventStore::new();
let mut projection = BalanceProjection::new();
// Apply events
let events = vec![
AccountOpened { id: "alice", balance: Money::new(1000) },
MoneyWithdrawn { account: "alice", amount: Money::new(100) },
];
for event in events {
projection.apply(&event).await.unwrap();
}
// Check projection state
assert_eq!(projection.balance("alice"), Money::new(900));
}
Limitations
The memory adapter is for testing only:
- ❌ No persistence - data lost on restart
- ❌ No distributed transactions
- ❌ Limited concurrency control compared to PostgreSQL
- ❌ No query capabilities beyond basic operations
For production, use eventcore-postgres.
Advanced Testing
Snapshot Testing
#[test]
fn test_with_snapshot() {
let store = MemoryEventStore::new();
// Take snapshot
let snapshot = store.snapshot();
// Make changes
store.append_events(...).await.unwrap();
// Restore snapshot
store.restore(snapshot);
// Store is back to original state
}
Chaos Testing
#[test]
async fn test_random_failures() {
let store = MemoryEventStore::with_chaos(ChaosConfig {
failure_rate: 0.1, // 10% chance of failure
latency: Some(Duration::from_millis(100)),
});
// Test your error handling
}
Performance Testing
#[test]
async fn benchmark_command_throughput() {
let store = MemoryEventStore::new();
let executor = CommandExecutor::new(store);
let start = Instant::now();
for i in 0..10_000 {
executor.execute(TestCommand { id: i }).await.unwrap();
}
let elapsed = start.elapsed();
println!("Commands/sec: {}", 10_000.0 / elapsed.as_secs_f64());
}
Integration with Test Frameworks
With rstest
use rstest::*;
#[fixture]
fn event_store() -> MemoryEventStore {
MemoryEventStore::new()
}
#[fixture]
fn executor(event_store: MemoryEventStore) -> CommandExecutor<MemoryEventStore> {
CommandExecutor::new(event_store)
}
#[rstest]
#[tokio::test]
async fn test_with_fixtures(executor: CommandExecutor<MemoryEventStore>) {
// Your test here
}
With proptest
use proptest::prelude::*;
proptest! {
#[test]
fn transfer_properties(
amount in 1..1000u64,
initial in 1000..10000u64
) {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let store = MemoryEventStore::new();
// Property-based testing
});
}
}
Debugging
Enable trace logging to see all operations:
#[test]
fn debug_test() {
let _ = env_logger::builder()
.filter_level(log::LevelFilter::Trace)
.try_init();
let store = MemoryEventStore::new();
// All operations will be logged
}
See Also
- EventCore Core - Core library documentation
- Test Utilities - Testing helpers
- Examples - Complete test examples
Dependencies
~10–15MB
~265K SLoC