1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
//! MockMe is a tool used to mock dependencies / function calls when running unit (lib) tests in Rust. //! //! ## How to Use //! //! Simply use the macro as seen in the example below. //! When this code is run normally, MockMe will have no effect. //! However, when the code is run as part of a unit test #[cfg(test)], //! the mocked token will be used instead. //! //! In order to use, we first must mark the functions that we would like to mock. //! When running tests, we then identify the replacement function that we would like to inject. //! //! ```rust,ignore //! //! #![feature(proc_macro)] //! extern crate mock_me; //! use mock_me::{mock, inject}; //! //! // Below we will create two mocking identifiers called id_1 and id_2. //! // We will then provide the name of the two functions we are mocking, as well as //! // their type signature. In future iterations, hopefully the signature won't be needed. //! #[mock(id_1="external_db_call: fn(u32) -> String", id_2="other_call: fn() -> String")] //! fn my_super_cool_function() -> String { //! let input = 42u32; //! // external_db_call will be replaced with fake_mocked_call during testing //! let db_result = external_db_call(input); //! //! // other_call will also be replaced //! let other_result = other_call(); //! format!("I have two results! {} and {}", db_result, other_result) //! } //! //! // Finally, when we run our tests, we simply need to provide the identifier we previously used, //! // as well as the name of the replacement function //! #[test] //! #[inject(id_1="db_fake", id_2="other_fake")] //! fn actual_test2() { //! let result = my_super_cool_function(); //! assert_eq!(result, "I have two results! Faker! and This is indeed a disturbing universe."); //! } //! //! fn db_fake(_: u32) -> String { "Faker!".to_string() } //! fn other_fake() -> String { "This is indeed a disturbing universe.".to_string() } //! //! ``` #![feature(proc_macro)] extern crate proc_macro; use proc_macro::TokenStream; use std::fmt::Write; mod macro_helper; use macro_helper::*; /// The mock macro is used mock a concrete function that is not desired during unit tests. /// Its signature contains the identifier that is being mocked, with the function that will replace /// the mocked function within quotes, as well as the mocked function signature. #[proc_macro_attribute] pub fn mock(attr: TokenStream, item: TokenStream) -> TokenStream { let mock_matches = get_mock_matches(&*attr.to_string()); let mut source = item.to_string(); // I should find a more structured way of injecting test context into top of method let insertion_point = source.find("{").unwrap() + 1; source.insert_str(insertion_point, HEADER); let mut modified_source = source.clone(); for m_match in mock_matches { let ctx_getter = format!(r#" (unsafe {{ let _mock_me_test_usize_func = _mock_me_test_context_instance.get("{}"); let _mock_me_test_transmuted_func: {} = std::mem::transmute(_mock_me_test_usize_func); _mock_me_test_transmuted_func }}) "#, m_match.identifier, m_match.function_signature); // string replacement should be more controlled ideally than a blind replace modified_source = modified_source.replace(&*m_match.function_to_mock, &*ctx_getter); } let branched_source = format!( r#" #[cfg(not(test))] {} #[cfg(test)] {} "#, source, modified_source ); branched_source.parse().unwrap() } /// The inject macro is used to replace a mocked function with an alternative implementation. /// Its signature contains the identifier that is being mocked, with the function that will replace /// the mocked function within quotes. #[proc_macro_attribute] pub fn inject(attr: TokenStream, item: TokenStream) -> TokenStream { let inject_matches = get_inject_matches(&*attr.to_string()); let mut source = item.to_string(); let mut context_setter_string = HEADER.to_string(); for i_match in inject_matches { write!( context_setter_string, "_mock_me_test_context_instance.set(\"{}\".to_string(), {} as usize);\n", i_match.identifier, i_match.function_to_mock ).unwrap(); } // I should find a more structured way of injecting test context into top of method let insertion_point = source.find("{").unwrap() + 1; source.insert_str(insertion_point, &*context_setter_string); source.parse().unwrap() }