From 301f7b8c9cdbec3566174cb6046659da4fe5747a Mon Sep 17 00:00:00 2001 From: Kai Hudalla Date: Fri, 15 Nov 2024 17:20:39 +0100 Subject: [PATCH] [#185] Add feature for exposing mock implementations The "test-util" feature has been added which exposed the mockall based mock implementations of the transport and communication level API traits. These can be helpful to e.g. crates implementing UTransport for implementing unit tests. --- Cargo.toml | 2 ++ src/communication.rs | 6 +++++ src/communication/notification.rs | 4 +-- src/communication/pubsub.rs | 4 +-- src/communication/rpc.rs | 41 ++++++++++++++++++++++++++++--- src/lib.rs | 12 ++++++--- src/utransport.rs | 14 +++++------ 7 files changed, 62 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 331c72d..6428bfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,13 @@ udiscovery = [] usubscription = [] utwin = [] util = ["tokio/sync"] +test-util = ["mockall"] [dependencies] async-trait = { version = "0.1" } bytes = { version = "1.7" } mediatype = "0.19" +mockall = { version = "0.13", optional = true } protobuf = { version = "3.5", features = ["with-bytes"] } rand = { version = "0.8" } thiserror = { version = "1.0", optional = true } diff --git a/src/communication.rs b/src/communication.rs index 3aa6c1a..86fb69f 100644 --- a/src/communication.rs +++ b/src/communication.rs @@ -20,9 +20,15 @@ pub use default_notifier::SimpleNotifier; pub use default_pubsub::{InMemorySubscriber, SimplePublisher}; pub use in_memory_rpc_client::InMemoryRpcClient; pub use in_memory_rpc_server::InMemoryRpcServer; +#[cfg(any(test, feature = "test-util"))] +pub use notification::MockNotifier; pub use notification::{NotificationError, Notifier}; +#[cfg(any(test, feature = "test-util"))] +pub use pubsub::MockSubscriptionChangeHandler; #[cfg(feature = "usubscription")] pub use pubsub::{PubSubError, Publisher, Subscriber}; +#[cfg(any(test, feature = "test-util"))] +pub use rpc::{MockRequestHandler, MockRpcClient, MockRpcServerImpl}; pub use rpc::{RequestHandler, RpcClient, RpcServer, ServiceInvocationError}; #[cfg(feature = "usubscription")] pub use usubscription_client::RpcClientUSubscription; diff --git a/src/communication/notification.rs b/src/communication/notification.rs index b7ae871..38c2ee9 100644 --- a/src/communication/notification.rs +++ b/src/communication/notification.rs @@ -14,8 +14,6 @@ use std::{error::Error, fmt::Display, sync::Arc}; use async_trait::async_trait; -#[cfg(test)] -use mockall::automock; use crate::communication::RegistrationError; use crate::{UListener, UStatus, UUri}; @@ -50,7 +48,7 @@ impl Error for NotificationError {} /// Please refer to the /// [Communication Layer API Specifications](https://github.com/eclipse-uprotocol/up-spec/blob/main/up-l2/api.adoc). // [impl->req~up-language-comm-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait] pub trait Notifier: Send + Sync { /// Sends a notification to a uEntity. diff --git a/src/communication/pubsub.rs b/src/communication/pubsub.rs index c9a90fd..bf27d94 100644 --- a/src/communication/pubsub.rs +++ b/src/communication/pubsub.rs @@ -14,8 +14,6 @@ use std::{error::Error, fmt::Display, sync::Arc}; use async_trait::async_trait; -#[cfg(test)] -use mockall::automock; use crate::communication::RegistrationError; use crate::core::usubscription::SubscriptionStatus; @@ -73,7 +71,7 @@ pub trait Publisher: Send + Sync { } // [impl->req~up-language-comm-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] pub trait SubscriptionChangeHandler: Send + Sync { /// Invoked for each update to the subscription status for a given topic. /// diff --git a/src/communication/rpc.rs b/src/communication/rpc.rs index 79c8706..4a6ee93 100644 --- a/src/communication/rpc.rs +++ b/src/communication/rpc.rs @@ -15,8 +15,6 @@ use std::sync::Arc; use thiserror::Error; use async_trait::async_trait; -#[cfg(test)] -use mockall::automock; use protobuf::MessageFull; use crate::communication::RegistrationError; @@ -139,7 +137,7 @@ impl From for UStatus { /// [Communication Layer API specification](https://github.com/eclipse-uprotocol/up-spec/blob/main/up-l2/api.adoc) /// for details. // [impl->req~up-language-comm-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait] pub trait RpcClient: Send + Sync { /// Invokes a method on a service. @@ -213,7 +211,7 @@ impl dyn RpcClient { /// A handler for processing incoming RPC requests. /// // [impl->req~up-language-comm-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait] pub trait RequestHandler: Send + Sync { /// Handles a request to invoke a method with given input parameters. @@ -291,6 +289,41 @@ pub trait RpcServer { ) -> Result<(), RegistrationError>; } +#[cfg(any(test, feature = "test-util"))] +mockall::mock! { + /// This extra struct is necessary in order to comply with mockall's requirements regarding the parameter lifetimes + /// see + pub RpcServerImpl { + pub async fn do_register_endpoint<'a>(&'a self, origin_filter: Option<&'a UUri>, resource_id: u16, request_handler: Arc) -> Result<(), RegistrationError>; + pub async fn do_unregister_endpoint<'a>(&'a self, origin_filter: Option<&'a UUri>, resource_id: u16, request_handler: Arc) -> Result<(), RegistrationError>; + } +} + +#[cfg(any(test, feature = "test-util"))] +#[async_trait] +/// This delegates the invocation of the UTransport functions to the mocked functions of the Transport struct. +/// see +impl RpcServer for MockRpcServerImpl { + async fn register_endpoint( + &self, + origin_filter: Option<&UUri>, + resource_id: u16, + request_handler: Arc, + ) -> Result<(), RegistrationError> { + self.do_register_endpoint(origin_filter, resource_id, request_handler) + .await + } + async fn unregister_endpoint( + &self, + origin_filter: Option<&UUri>, + resource_id: u16, + request_handler: Arc, + ) -> Result<(), RegistrationError> { + self.do_unregister_endpoint(origin_filter, resource_id, request_handler) + .await + } +} + #[cfg(test)] mod tests { use std::sync::Arc; diff --git a/src/lib.rs b/src/lib.rs index 3ea5898..729a3e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ For user convenience, all of these modules export their types on up_rust top-lev implementations. Enabled by default. * `utwin` enables support for types required to interact with [uTwin service](https://raw.githubusercontent.com/eclipse-uprotocol/up-spec/v1.6.0-alpha.3/up-l3/utwin/v3/README.adoc) implementations. +* `test-util` provides some useful mock implementations for testing. In particular, provides mock implementations of UTransport and Communication Layer API traits which make implementing unit tests a lot easier. * `util` provides some useful helper structs. In particular, provides a local, in-memory UTransport for exchanging messages within a single process. This transport is also used by the examples illustrating usage of the Communication Layer API. ## References @@ -57,14 +58,16 @@ For user convenience, all of these modules export their types on up_rust top-lev // up_core_api types used and augmented by up_rust - symbols re-exported to toplevel, errors are module-specific #[cfg(feature = "communication")] pub mod communication; + #[cfg(feature = "util")] pub mod local_transport; + mod uattributes; pub use uattributes::{ - NotificationValidator, PublishValidator, RequestValidator, ResponseValidator, - UAttributesValidator, UAttributesValidators, + NotificationValidator, PublishValidator, RequestValidator, ResponseValidator, UAttributes, + UAttributesError, UAttributesValidator, UAttributesValidators, UMessageType, UPayloadFormat, + UPriority, }; -pub use uattributes::{UAttributes, UAttributesError, UMessageType, UPayloadFormat, UPriority}; mod umessage; pub use umessage::{UMessage, UMessageBuilder, UMessageError}; @@ -79,6 +82,9 @@ mod utransport; pub use utransport::{ ComparableListener, LocalUriProvider, StaticUriProvider, UListener, UTransport, }; +#[cfg(feature = "test-util")] +pub use utransport::{MockLocalUriProvider, MockTransport, MockUListener}; + mod uuid; pub use uuid::UUID; diff --git a/src/utransport.rs b/src/utransport.rs index 158f27b..f3692ef 100644 --- a/src/utransport.rs +++ b/src/utransport.rs @@ -18,8 +18,6 @@ use std::ops::Deref; use std::sync::Arc; use async_trait::async_trait; -#[cfg(test)] -use mockall::automock; use crate::{UCode, UMessage, UStatus, UUri}; @@ -28,7 +26,7 @@ use crate::{UCode, UMessage, UStatus, UUri}; /// Implementations may use arbitrary mechanisms to determine the information that /// is necessary for creating URIs, e.g. environment variables, configuration files etc. // [impl->req~up-language-transport-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] pub trait LocalUriProvider: Send + Sync { /// Gets the _authority_ used for URIs representing this uEntity's resources. fn get_authority(&self) -> String; @@ -149,7 +147,7 @@ impl TryFrom<&UUri> for StaticUriProvider { /// Please refer to the [uProtocol Transport Layer specification](https://github.com/eclipse-uprotocol/up-spec/blob/v1.6.0-alpha.3/up-l1/README.adoc) /// for details. // [impl->req~up-language-transport-api~1] -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-util"), mockall::automock)] #[async_trait] pub trait UListener: Send + Sync { /// Performs some action on receipt of a message. @@ -273,10 +271,10 @@ pub trait UTransport: Send + Sync { } } -#[cfg(test)] +#[cfg(any(test, feature = "test-util"))] mockall::mock! { /// This extra struct is necessary in order to comply with mockall's requirements regarding the parameter lifetimes - /// see https://github.com/asomers/mockall/issues/571 + /// see pub Transport { pub async fn do_send(&self, message: UMessage) -> Result<(), UStatus>; pub async fn do_register_listener<'a>(&'a self, source_filter: &'a UUri, sink_filter: Option<&'a UUri>, listener: Arc) -> Result<(), UStatus>; @@ -284,10 +282,10 @@ mockall::mock! { } } -#[cfg(test)] +#[cfg(any(test, feature = "test-util"))] #[async_trait] /// This delegates the invocation of the UTransport functions to the mocked functions of the Transport struct. -/// see https://github.com/asomers/mockall/issues/571 +/// see impl UTransport for MockTransport { async fn send(&self, message: UMessage) -> Result<(), UStatus> { self.do_send(message).await