Testing a service
The #[marine_test] macro also allows testing data flow between multiple services, so you do not need to deploy anything to the network and write an Aqua app just for basic testing. Let's look at an example:
- test.rs
- producer.rs
- consumer.rs
- test_on_mod.rs
rustfn main() {}#[cfg(test)]mod tests {use marine_rs_sdk_test::marine_test;#[marine_test( // 1producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new(); // 2let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::Input { // 3first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.produce(input); // 4let result = consumer.consume(data);assert_eq!(result, "John Doe")}}
rustfn main() {}#[cfg(test)]mod tests {use marine_rs_sdk_test::marine_test;#[marine_test( // 1producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new(); // 2let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::Input { // 3first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.produce(input); // 4let result = consumer.consume(data);assert_eq!(result, "John Doe")}}
rustuse marine_rs_sdk::marine;use marine_rs_sdk::module_manifest;module_manifest!();pub fn main() {}#[marine]pub struct Data {pub name: String,}#[marine]pub struct Input {pub first_name: String,pub last_name: String,}#[marine]pub fn produce(data: Input) -> Data {Data {name: format!("{} {}", data.first_name, data.last_name),}}
rustuse marine_rs_sdk::marine;use marine_rs_sdk::module_manifest;module_manifest!();pub fn main() {}#[marine]pub struct Data {pub name: String,}#[marine]pub struct Input {pub first_name: String,pub last_name: String,}#[marine]pub fn produce(data: Input) -> Data {Data {name: format!("{} {}", data.first_name, data.last_name),}}
rustuse marine_rs_sdk::marine;use marine_rs_sdk::module_manifest;module_manifest!();pub fn main() {}#[marine]pub struct Data {pub name: String,}#[marine]pub fn consume(data: Data) -> String {data.name}
rustuse marine_rs_sdk::marine;use marine_rs_sdk::module_manifest;module_manifest!();pub fn main() {}#[marine]pub struct Data {pub name: String,}#[marine]pub fn consume(data: Data) -> String {data.name}
rustfn main() {}#[cfg(test)]#[marine_rs_sdk_test::marine_test(producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]mod tests_on_mod {#[test]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new();let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::Input {first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.produce(input);let result = consumer.consume(data);assert_eq!(result, "John Doe")}}
rustfn main() {}#[cfg(test)]#[marine_rs_sdk_test::marine_test(producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]mod tests_on_mod {#[test]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new();let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::Input {first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.produce(input);let result = consumer.consume(data);assert_eq!(result, "John Doe")}}
- We wrap the
testfunction with themarine_testmacro by providing named service configurations with module locations. Based on its arguments the macro defines amarine_test_envmodule with an interface to the services. - We create new services. Each
ServiceInterface::new()runs a new marine runtime with the service. - We prepare data to pass to a service using structure definition from
marine_test_env. The macro finds all structures used in the service interface functions and defines them in the corresponding submodule ofmarine_test_env. - We call a service function through the
ServiceInterfaceobject. - It is possible to use the result of one service call as an argument for a different service call. The interface types with the same structure have the same rust type in
marine_test_env.
In the test_on_mod.rs tab we can see another option — applying marine_test to a mod. The macro just defines the marine_test_env at the beginning of the module and then it can be used as usual everywhere inside the module.
The full example is here.
The marine_test macro also gives access to the interface of internal modules which may be useful for setting up a test environment. This feature is designed to be used in situations when it is simpler to set up a service for a test through internal functions than through the service interface. To illustrate this feature we have rewritten the previous example:
rustfn main() {}#[cfg(test)]mod tests {use marine_rs_sdk_test::marine_test;#[marine_test(producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new();let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::modules::producer::Input { // 1first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.modules.producer.produce(input); // 2let consumer_data = marine_test_env::consumer::modules::consumer::Data { name: data.name } // 3;let result = consumer.modules.consumer.consume(consumer_data);assert_eq!(result, "John Doe")}}
rustfn main() {}#[cfg(test)]mod tests {use marine_rs_sdk_test::marine_test;#[marine_test(producer(config_path = "../producer/Config.toml",modules_dir = "../producer/artifacts"),consumer(config_path = "../consumer/Config.toml",modules_dir = "../consumer/artifacts"))]fn test() {let mut producer = marine_test_env::producer::ServiceInterface::new();let mut consumer = marine_test_env::consumer::ServiceInterface::new();let input = marine_test_env::producer::modules::producer::Input { // 1first_name: String::from("John"),last_name: String::from("Doe"),};let data = producer.modules.producer.produce(input); // 2let consumer_data = marine_test_env::consumer::modules::consumer::Data { name: data.name } // 3;let result = consumer.modules.consumer.consume(consumer_data);assert_eq!(result, "John Doe")}}
- We access the internal service interface to construct an interface structure. To do so, we use the following pattern:
marine_test_env::$service_name::modules::$module_name::$structure_name. - We access the internal service interface and directly call a function from one of the modules of this service. To do so, we use the following pattern:
$service_object.modules.$module_name.$function_name. - In the previous example, the same interface types had the same rust types. It is limited when using internal modules: the property is true only when structures are defined in internal modules of one service, or when structures are defined in service interfaces of different services. So, we need to construct the proper type to pass data to the internals of another module.