unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP
Mocking

Mocking #

Mocking dalam konteks pengembangan perangkat lunak adalah praktik membuat objek tiruan (mock objects) yang mensimulasikan perilaku dari objek asli yang kompleks, seperti dependensi eksternal, modul jaringan, atau database. Ini berguna dalam pengujian unit (unit testing) untuk memastikan bahwa bagian tertentu dari kode bekerja dengan benar tanpa harus bergantung pada seluruh sistem.

Rust, sebagai bahasa yang fokus pada kinerja dan keamanan memori, memiliki pendekatan khusus dalam hal mocking, terutama karena Rust tidak mendukung mekanisme seperti inheritance dan dynamic dispatch secara langsung seperti di beberapa bahasa OOP lainnya. Mocking dalam Rust biasanya dilakukan menggunakan traits dan beberapa crate atau pustaka pihak ketiga yang menyediakan alat untuk mocking.

Pendekatan Dasar untuk Mocking di Rust #

Karena Rust adalah bahasa yang kuat dalam tipe dan tidak memiliki fitur bawaan untuk mocking seperti beberapa bahasa OOP, mocking biasanya dilakukan dengan memanfaatkan traits. Traits memungkinkan Anda mendefinisikan perilaku yang kemudian bisa diimplementasikan oleh mock structs dalam pengujian.

Menggunakan Traits untuk Mocking #

Langkah pertama dalam mocking di Rust adalah mendefinisikan sebuah trait yang mendeklarasikan perilaku atau metode yang diperlukan oleh kode Anda. Kemudian, Anda dapat mengimplementasikan trait tersebut dalam objek asli dan mock.

Contoh Penggunaan Traits untuk Mocking:

trait DataSource {
    fn fetch_data(&self) -> String;
}

struct RealDataSource;

impl DataSource for RealDataSource {
    fn fetch_data(&self) -> String {
        // Misalnya, melakukan panggilan jaringan atau membaca dari database
        "Data asli dari sumber data".to_string()
    }
}

struct MockDataSource;

impl DataSource for MockDataSource {
    fn fetch_data(&self) -> String {
        // Mengembalikan data tiruan untuk pengujian
        "Data mock".to_string()
    }
}

fn process_data(source: &impl DataSource) {
    let data = source.fetch_data();
    println!("Memproses: {}", data);
}

fn main() {
    let real_source = RealDataSource;
    let mock_source = MockDataSource;

    process_data(&real_source); // Memanggil dengan sumber data asli
    process_data(&mock_source); // Memanggil dengan mock
}

Penjelasan:

  • DataSource adalah trait yang mendefinisikan metode fetch_data.
  • RealDataSource mengimplementasikan DataSource untuk menghasilkan data nyata.
  • MockDataSource mengimplementasikan DataSource untuk menghasilkan data tiruan yang dapat digunakan dalam pengujian.
  • Fungsi process_data menerima referensi ke tipe apa pun yang mengimplementasikan DataSource, memungkinkan penggunaan baik RealDataSource maupun MockDataSource.

Menggunakan Crate untuk Mocking #

Meskipun pendekatan manual menggunakan traits sudah cukup kuat, ada beberapa crate yang mempermudah dan memperkaya kemampuan mocking di Rust. Beberapa crate populer untuk mocking di Rust termasuk mockall, mockito, dan double.

Mocking dengan mockall #

mockall adalah crate yang menyediakan fasilitas lengkap untuk mocking di Rust, termasuk untuk memock trait, fungsi bebas, dan metode statis. mockall mendukung mocking pada level compile-time dan runtime.

Instalasi mockall:

Tambahkan crate mockall ke dalam Cargo.toml:

[dependencies]
mockall = "0.10.2"

Contoh Penggunaan mockall:

use mockall::predicate::*;
use mockall::*;

#[automock]
trait DataSource {
    fn fetch_data(&self) -> String;
}

fn process_data(source: &impl DataSource) -> String {
    source.fetch_data()
}

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

    #[test]
    fn test_process_data_with_mock() {
        let mut mock_source = MockDataSource::new();
        mock_source.expect_fetch_data()
                   .returning(|| "Data mock".to_string());

        let result = process_data(&mock_source);
        assert_eq!(result, "Data mock");
    }
}

Penjelasan:

  • #[automock] digunakan untuk secara otomatis menghasilkan mock untuk trait DataSource.
  • MockDataSource adalah versi mock dari DataSource yang dihasilkan oleh mockall.
  • expect_fetch_data digunakan untuk menentukan perilaku mock ketika metode fetch_data dipanggil.
  • returning menentukan nilai yang dikembalikan oleh mock saat metode tersebut dipanggil.

Mocking dengan mockito #

mockito adalah crate yang memungkinkan Anda untuk memock server HTTP untuk pengujian. Ini berguna untuk menguji kode yang berinteraksi dengan API eksternal tanpa memerlukan koneksi jaringan nyata.

Instalasi mockito:

Tambahkan crate mockito ke dalam Cargo.toml:

[dependencies]
mockito = "0.31"

Contoh Penggunaan mockito:

use mockito::{mock, Matcher};
use reqwest;

fn fetch_data_from_api() -> Result<String, reqwest::Error> {
    let url = &format!("{}/data", mockito::server_url());
    let resp = reqwest::blocking::get(url)?.text()?;
    Ok(resp)
}

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

    #[test]
    fn test_fetch_data_from_api() {
        let _mock = mock("GET", "/data")
            .with_status(200)
            .with_body("mocked response")
            .create();

        let result = fetch_data_from_api().unwrap();
        assert_eq!(result, "mocked response");
    }
}

Penjelasan:

  • mockito::mock digunakan untuk membuat mock server HTTP yang merespons permintaan GET ke /data dengan status 200 dan body "mocked response".
  • Fungsi fetch_data_from_api berinteraksi dengan server mock yang disiapkan oleh mockito.

Teknik Lain untuk Mocking #

Dependency Injection #

Dependency Injection (DI) adalah teknik umum dalam pengembangan perangkat lunak yang memungkinkan Anda menyuntikkan dependensi ke dalam objek atau fungsi alih-alih membuatnya secara langsung. Ini sangat mendukung testing dan mocking.

Contoh Dependency Injection:

struct Service<T: DataSource> {
    source: T,
}

impl<T: DataSource> Service<T> {
    fn new(source: T) -> Self {
        Self { source }
    }

    fn process_data(&self) -> String {
        self.source.fetch_data()
    }
}

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

    struct MockDataSource;

    impl DataSource for MockDataSource {
        fn fetch_data(&self) -> String {
            "Mocked data".to_string()
        }
    }

    #[test]
    fn test_service_with_mock() {
        let mock_source = MockDataSource;
        let service = Service::new(mock_source);

        assert_eq!(service.process_data(), "Mocked data");
    }
}

Penjelasan:

  • Service<T> adalah struct generik yang menerima objek T yang mengimplementasikan trait DataSource.
  • Service::new menerima objek T yang disuntikkan dari luar, memungkinkan pengujian dengan mock.

Mocking dalam Konteks Asynchronous #

Rust mendukung asynchronous programming melalui async/await, dan mocking dalam konteks ini memerlukan perhatian khusus.

Contoh Mocking Asynchronous dengan mockall:

use mockall::predicate::*;
use mockall::*;

#[automock]
#[async_trait::async_trait]
trait AsyncDataSource {
    async fn fetch_data(&self) -> String;
}

async fn process_data(source: &impl AsyncDataSource) -> String {
    source.fetch_data().await
}

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

    #[tokio::test]
    async fn test_process_data_with_mock() {
        let mut mock_source = MockAsyncDataSource::new();
        mock_source.expect_fetch_data()
                   .returning(|| Box::pin(async { "Data mock".to_string() }));

        let result = process_data(&mock_source).await;
        assert_eq!(result, "Data mock");
    }
}

Penjelasan:

  • #[async_trait::async_trait] memungkinkan Anda untuk mendefinisikan trait asynchronous.
  • Box::pin(async { ... }) digunakan untuk membuat Future tiruan yang dapat di-return oleh mock.

Kesimpulan #

Mocking di Rust adalah teknik yang berguna untuk mengisolasi unit kode dalam pengujian, sehingga Anda dapat memastikan bahwa bagian tertentu dari kode Anda bekerja dengan benar tanpa memerlukan dependensi eksternal yang kompleks. Rust tidak memiliki fitur mocking bawaan seperti beberapa bahasa OOP lainnya, tetapi dengan memanfaatkan traits dan crate seperti mockall, mockito, dan lainnya, Anda dapat dengan mudah melakukan mocking di Rust.

Memahami dan menggunakan mocking dengan benar akan membantu Anda menulis tes unit yang lebih kuat dan terisolasi, serta memungkinkan Anda untuk membangun aplikasi yang lebih dapat diandalkan.

« Unit Test
JSON »