Is this a correct implementation of the Repository Pattern and testing in Rust using Diesel?

Hi everyone,

I'm working on a Rust project using Diesel for database interactions, and I'm trying to implement the Repository Pattern to abstract the database layer and make it easier to test. I also want to mock the repository for unit testing. I've come up with an implementation, but I'm not entirely sure if it's the right way to do it. I'd appreciate any feedback or suggestions for improvement.

Here's a simplified version of my code:

//user_repository.rs

use diesel::QueryResult;
use crate::models::{User, NewUser};

pub trait UserRepository{
    fn create_user(&self, user: NewUser) -> QueryResult<User>;
    fn get_user_by_id(&self, id: i32) -> QueryResult<User>;
}

//user_diesel_repository.rs

use crate::{
    PGPool,
    models::{User, NewUser},
    schema::users,
};
use diesel::prelude::*;
use super::users_repository::UserRepository;

pub struct UsersDieselRepository{
    pub pool: PGPool
}


impl UserRepository for UsersDieselRepository {
    fn create_user(&self, new_user: NewUser) -> QueryResult<User> {
       
        let mut conn = self.pool.get().expect("Failed to get connection from pool");

        diesel::insert_into(users::table)
            .values(&new_user)
            .get_result(&mut conn)

    }

    fn get_user_by_id(&self, id: i32) -> QueryResult<User> {
       
        let mut conn = self.pool.get().expect("Failed to get connection from pool");

        users::table 
            .filter(users::id.eq(id))
            .select(User::as_select())
            .first(&mut conn)
    }

}

//user_service.rs

use crate::{
    repository::UserRepository,
    models::{User, NewUser},
};

pub struct UserService<R: UserRepository> {
    pub user_repository: R
}   


impl <R: UserRepository> UserService<R> {
    pub fn new(user_repository: R) -> Self {
        Self {
            user_repository
        }
    }

    pub fn create_user_service(&self, new_user: NewUser) -> Result<User, String> {
        match self.user_repository.create_user(new_user){
            Ok(user) => Ok(user),
            Err(err) => Err(err.to_string())
        }
    }
}

//Tests

#[cfg(test)]
mod user_service_test{
    use super::*;

    #[warn(dead_code)]
    struct MockUserRepository;

    impl UserRepository for MockUserRepository{
        fn create_user(&self, user: NewUser) -> diesel::QueryResult<User> {
            Ok(
                User { 
                    id: 1, username: "Thiago".to_string(), email: "thiago@test.com".to_string(), password_hash: "password_hashed".to_string(), is_active: false, created_at: chrono::NaiveDateTime::from_timestamp_opt(1695216000, 0).unwrap()
                }
            )
        }

        fn get_user_by_id(&self, id: i32) -> diesel::QueryResult<User> {
            Ok(
                User {
                    id: 1, username: "Thiago".to_string(), email: "thiago@test.com".to_string(), password_hash: "password_hashed".to_string(), is_active: false, created_at: chrono::NaiveDateTime::from_timestamp_opt(1695216000, 0).unwrap()
                }
            )
        }
    } 

    #[warn(dead_code)]
    fn test_create_user_service_success() {
        let mock_user_repository = MockUserRepository;
        let user_service = UserService::new(mock_user_repository);

        let new_user = NewUser {
            username: "John Yoo".to_string(),
            email: "johnyoo@example.com".to_string(),
            password_hash: "hashed_password".to_string(),
            is_active: false,
        };

        let result = user_service.create_user_service(new_user);
        assert!(result.is_ok());
        assert_eq!(result.unwrap().id, 2);
    }

    #[warn(dead_code)]
    fn test_create_user_service_failure() {
        
        let mock_user_repository = MockUserRepository;
        let user_service = UserService::new(mock_user_repository);
        let new_user = NewUser {
            username: "John Yoo".to_string(),
            email: "johnyoo@example.com".to_string(),
            password_hash: "hashed_password".to_string(),
            is_active: false,
        };
        let result = user_service.create_user_service(new_user);
        assert!(result.is_err());
    }
    
}

About questions

  1. Testing: How can I properly test the failure case (e.g., when create_user returns an error)? Should I create a separate mock for failures, or is there a better way?

  2. Mocking: Is manually implementing the mock (like MockUserRepository) the best approach, or should I use a mocking library like mockall?

Any advice or examples would be greatly appreciated!

Thanks in advance!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.