Testing
Unit and integration testing strategies for NestForge applications.
Automated testing is a critical part of software development. NestForge provides a set of testing utilities that allow for efficient unit testing and robust integration testing by leveraging a testing module system.
The TestFactory
Section titled “The TestFactory”The TestFactory is the primary entry point for creating a testing environment. It allows you to create a mock-vibe of your application where you can override providers (like databases or third-party services) before building the container.
#[tokio::test]async fn test_users_service() { let module = nestforge::TestFactory::<AppModule>::create() .override_provider(MockDatabase::default()) .build() .expect("Failed to build testing module");
let service = module.resolve::<UsersService>().expect("Could not resolve service"); let user = service.find_by_id(1).await;
assert!(user.is_ok());}Integration Testing (End-to-End)
Section titled “Integration Testing (End-to-End)”Integration tests allow you to test the entire request/response cycle, including routing, validation, and middleware.
NestForge’s TestingModule can produce an http_router() which is a standard Axum router. You can use the tower::ServiceExt trait to send simulated requests to it.
use tower::ServiceExt; // for .oneshot()use axum::http::{Request, StatusCode};use axum::body::Body;
#[tokio::test]async fn test_get_users_route() { let module = nestforge::TestFactory::<AppModule>::create().build().unwrap(); let app = module.http_router();
let response = app .oneshot( Request::builder() .uri("/users") .body(Body::empty()) .unwrap(), ) .await .unwrap();
assert_eq!(response.status(), StatusCode::OK);}Overriding Providers
Section titled “Overriding Providers”One of the most powerful features of the NestForge testing system is the ability to swap out actual implementations with mocks or stubs.
let module = nestforge::TestFactory::<AppModule>::create() // Replace the real MailService with a mock that doesn't send real emails .override_provider(MockMailService::new()) .build() .unwrap();Testing Other Transports
Section titled “Testing Other Transports”The testing module isn’t limited to HTTP. You can create specialized contexts for any transport:
- GraphQL:
module.graphql_router(schema) - WebSockets:
module.websocket_context() - gRPC:
module.grpc_context()
This ensures that your transport-specific logic (like gRPC interceptors or WebSocket auth) can be tested in isolation but with full DI support.
Best Practices
Section titled “Best Practices”- Use
oneshotfor HTTP Tests: It’s faster than starting a real TCP server and sufficient for 99% of integration tests. - Clean up with
shutdown(): If your modules use lifecycle hooks (like closing DB pools), callmodule.shutdown()at the end of your test. - Mock Externally: Always override providers that make external network calls (Stripe, Twilio, AWS) to keep your tests fast and deterministic.