Let's add some integration tests
Good to know
How to write integration tests in Rust.
Implementation
Add a new folder named tests
beside to src
.
.
βββ Cargo.lock
βββ Cargo.toml
βββ README.md
βββ src
βββ target
βββ tests
Add tests/test_utils.rs
file to have a place for common test utilities. Although there are some debates what is the best way to share utilities between tests, I found this way simple and effective. Add your utilities to tests/test_utilities.rs
and use test_utilities;
in a test file you need them. Talk is cheap, let's write the code:
We need a function to create a directory manager for us, but we need to create a random name for the work_tree
directory. That's why we're going to use uuid
crate. Either add it manually to your Cargo.toml or run cargo add uuid --features=v4
Now let's create a DirectoryManager
object with a randomly generated temp path for its base directory.
// tests/test_utilities.rs
#[cfg(test)]
pub mod directory_manager {
use rit::DirectoryManager;
use super::general::generate_random_path;
pub fn create_directory_manager() -> DirectoryManager {
rit::DirectoryManager::new(generate_random_path())
}
}
#[cfg(test)]
pub mod general {
use std::path::PathBuf;
pub fn generate_random_path() -> PathBuf {
std::env::temp_dir().join(uuid::Uuid::new_v4().to_string())
}
}
OK! We're ready to add our first integration tests. Why didn't we add them as unit tests? Because we're going to play with filesystem to see if it works in wild!
Add a new file tests/directory_manager.rs
. Add a test to see what happens if we call create_directory_tree
. Does it really create some folders for us?
// To be able to use test_utils. Each file in tests directory will become a separate crate/executable.
mod test_utils;
use test_utils::directory_manager::create_directory_manager;
#[test]
fn directory_tree_should_be_created_successfully() {
let dir_manager = create_directory_manager();
dir_manager.create_directory_tree().unwrap();
assert!(dir_manager.dot_git_path.exists());
assert!(dir_manager.branches_path.exists());
assert!(dir_manager.objects_path.exists());
assert!(dir_manager.refs_heads_path.exists());
assert!(dir_manager.refs_tags_path.exists());
}
Next test! If we have an empty base_directory, is_dot_git_empty
should happily return true. Because we don't .git
#[test]
fn if_work_tree_directory_is_empty_is_dot_git_empty_should_return_true() {
let dir_manager = create_directory_manager();
std::fs::create_dir_all(&dir_manager.work_tree).unwrap();
assert!(dir_manager.is_dot_git_empty().unwrap());
}
If .git exists but it's empty, is_dot_git_empty
should return true too.
#[test]
fn if_dot_git_is_empty_is_dot_git_empty_should_return_true() {
let dir_manager = DirectoryManager::new(temp_dir().join(Uuid::new_v4().to_string()));
// To create only .git file. Nothing inside it!
std::fs::create_dir_all(&dir_manager.dot_git_path).unwrap();
assert!(dir_manager.is_dot_git_empty().unwrap());
}
And finally if .git is not empty, is_dot_git_empty
should return false:
#[test]
fn if_dot_git_is_not_empty_is_dot_git_empty_should_return_false() {
let dir_manager = create_directory_manager();
// To create .git folder and all of its children.
dir_manager.create_directory_tree().unwrap();
assert!(!dir_manager.is_dot_git_empty().unwrap());
}
Run them with cargo test
. Failures! Let's make these tests happy.
// src/directory_manager.rs
pub struct DirectoryManager {
// ...
// Add some new fields.
pub branches_path: PathBuf,
pub objects_path: PathBuf,
pub refs_tags_path: PathBuf,
pub refs_heads_path: PathBuf,
}
impl DirectoryManager {
pub fn new<T: Into<PathBuf>>(base_path: T) -> Self {
let base_path: PathBuf = base_path.into();
let dot_git_path = base_path.join(".git");
// Initialize them
Self {
//...
branches_path: dot_git_path.join("branches"),
objects_path: dot_git_path.join("objects"),
refs_tags_path: dot_git_path.join("refs").join("tags"),
refs_heads_path: dot_git_path.join("refs").join("heads"),
dot_git_path,
}
}
pub fn is_dot_git_empty(&self) -> Result<bool, std::io::Error> {
Ok(!self.dot_git_path.exists() || self.dot_git_path.read_dir()?.next().is_none())
}
pub fn create_directory_tree(&self) -> Result<(), std::io::Error> {
//...
// Create them!
fs::create_dir_all(&self.branches_path)?;
fs::create_dir_all(&self.objects_path)?;
fs::create_dir_all(&self.refs_heads_path)?;
fs::create_dir_all(&self.refs_tags_path)?;
Ok(())
}
}
I also added one more unit test to directory_manager
module. You can see it here.
Let's add a few tests for GitRepository
as well. Crate a new file tests/repository.rs
and a first test:
#[test]
fn if_project_directory_is_empty_create_should_be_successful() {
// Create an empty directory
let project_dir = test_utils::general::generate_random_path();
std::fs::create_dir_all(&project_dir).unwrap();
let repo = GitRepository::create(&project_dir).unwrap();
// Sub-directories should be created.
let dir_manager = &repo.directory_manager;
assert!(dir_manager.dot_git_path.exists());
assert!(dir_manager.branches_path.exists());
assert!(dir_manager.objects_path.exists());
assert!(dir_manager.refs_heads_path.exists());
assert!(dir_manager.refs_tags_path.exists());
// .git/config should have default configs
assert_eq!(
fs::read_to_string(&dir_manager.config_file).unwrap(),
GitConfig::default_str()
);
}
The rest of the tests are self-explanatory. See them here