In the vast world of software engineering, testing plays an indispensable role in ensuring the reliability, stability, and performance of our applications. Among various testing types, integration testing stands out as a critical phase, verifying that different components of your system interact seamlessly. However, integration testing, especially when dealing with external dependencies like databases and message brokers, can be complex.
In this article, we will embark on a journey to master integration testing in Rust using a powerful tool known as Testcontainers. This tool simplifies the process of spinning up Docker containers for your integration tests, allowing you to test against real external systems in a clean and isolated environment.
π Getting Started: Installing Rust
Before diving into integration testing with Testcontainers, let's ensure that your Rust development environment is set up correctly. Rust, known as "the language of systems," provides a robust foundation for building fast and reliable software.
Installing Rust on Windows
Visit the official Rust website here.
Download the Windows installer and run it.
Follow the on-screen instructions to complete the installation.
Open Command Prompt or PowerShell and run the following command to verify the installation:
rustc --version
This command should display the Rust version, confirming a successful installation.
Installing Rust on macOS
Open Terminal, which comes pre-installed on macOS.
Install Rust using Homebrew, a popular package manager for macOS, with this command:
brew install rust
- After the installation completes, verify Rust's installation by running:
rustc --version
You should see the Rust version displayed in the terminal, confirming that Rust is now installed on your macOS system.
ποΈ Setting Up the Integration Testing Project
Assuming you have a typical Rust project structure, which includes src/main.rs
for your application code and tests/integration_test.rs
for integration tests, we can proceed to set up our integration testing project.
To get started, add the Testcontainers dependency to your Cargo.toml
file:
β― cargo add testcontainers
Updating crates.io index
Adding testcontainers v0.14.0 to dependencies.
Features:
- async-trait
- bollard
- conquer-once
- experimental
- signal-hook
- tokio
- watchdog
Updating crates.io index
[dependencies]
testcontainers = "0.14.0"
Now, we're ready to dive into the specifics of integration testing with Testcontainers, using PostgreSQL as example.
π Setting Up the Integration Testing Project
Assuming you have a typical Rust project structure, which looks like this:
my_rust_project/
|-- src/
| |-- main.rs
|-- tests/
| |-- integration_test.rs
-
src/main.rs
contains your application code. -
tests/integration_test.rs
is where we'll write our integration tests.
π³ Leveraging Testcontainers for Rust
Testcontainers provides an intuitive way to manage Docker containers for your integration tests. Let's explore how to use it effectively in Rust.
Testing PostgreSQL Integration
In your integration_test.rs
file, you can set up and test a PostgreSQL container with the following code:
...
#[test]
async fn it_works() {
let docker = clients::Cli::default();
// Define a PostgreSQL container image
let postgres_image = Postgres::default();
let pg_container = docker.run(postgres_image);
pg_container.start();
WaitFor::seconds(60);
// Get the PostgreSQL port
let pg_port = pg_container.get_host_port_ipv4(5432);
// Define the connection to the Postgress client
let (client, connection) = tokio_postgres::Config::new()
.user("postgres")
.password("postgres")
.host("localhost")
.port(pg_port)
.dbname("postgres")
.connect(tokio_postgres::NoTls)
.await
.unwrap();
// Spawn connection
tokio::spawn(async move {
if let Err(error) = connection.await {
eprintln!("Connection error: {}", error);
}
});
let _ = client
.batch_execute(
"
CREATE TABLE IF NOT EXISTS app_user (
id SERIAL PRIMARY KEY,
username VARCHAR UNIQUE NOT NULL,
password VARCHAR NOT NULL,
email VARCHAR UNIQUE NOT NULL
)
",
)
.await;
let _ = client
.execute(
"INSERT INTO app_user (username, password, email) VALUES ($1, $2, $3)",
&[&"user1", &"mypass", &"user@test.com"],
)
.await;
let result = client
.query("SELECT id, username, password, email FROM app_user", &[])
.await
.unwrap();
let users: Vec<User> = result.into_iter().map(|row| User::from(row)).collect();
let user = users.first().unwrap();
assert_eq!(1, user.id);
assert_eq!("user1", user.username);
assert_eq!("mypass", user.password);
assert_eq!("user@test.com", user.email);
}
In this test, we:
- Create a Docker client.
- Define a PostgreSQL container image.
- Run the PostgreSQL container, expose its ports, and set the user and password.
- Retrieve the PostgreSQL port and add it to the connection URL.
- Connect to the PostgresSQL DB using tokio_postgres
- We need to spawn a new connection
- Write your database integration test code using the
client
.
π Running the Integration Tests
To run your integration tests, use the following command:
β― cargo test --test integration_test
Compiling testcontainer_example v0.1.0 (/Users/sergiomarcial/Documents/Confidential/repositories/github/testcontainers-rust)
Finished test [unoptimized + debuginfo] target(s) in 1.76s
Running tests/integration_test.rs (target/debug/deps/integration_test-ff1cb10ac2b24a1b)
running 1 test
test it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.11s
This command will execute all the tests in your integration_test.rs
file.
Code example
For a complete example using Rust
and Postgres
testcontainers, check out my GitHub repository.
π Exploring Alternatives and Considerations
While Testcontainers simplifies integration testing, it's crucial to explore alternative approaches like mocking or using Docker Compose for more complex setups. Choose the approach that best aligns with your project's requirements.
π Conclusion
Integration testing in Rust can be made effortless and efficient with Testcontainers. You've learned how to set up your project, install dependencies, and write integration tests for PostgreSQL. This newfound knowledge empowers you to ensure that your Rust applications seamlessly interact with external dependencies, bolstering the robustness and reliability of your software. Happy testing!
π Further Reading
Integration testing has never been this enjoyable! π³π¦
Top comments (0)