Skip to content

Commit

Permalink
[#185] Add feature for exposing mock implementations
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sophokles73 committed Nov 19, 2024
1 parent 12bb625 commit 11c652f
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 21 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 6 additions & 0 deletions src/communication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions src/communication/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions src/communication/pubsub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
///
Expand Down
41 changes: 37 additions & 4 deletions src/communication/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -139,7 +137,7 @@ impl From<ServiceInvocationError> 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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <https://github.com/asomers/mockall/issues/571>
pub RpcServerImpl {
pub async fn do_register_endpoint<'a>(&'a self, origin_filter: Option<&'a UUri>, resource_id: u16, request_handler: Arc<dyn RequestHandler>) -> Result<(), RegistrationError>;
pub async fn do_unregister_endpoint<'a>(&'a self, origin_filter: Option<&'a UUri>, resource_id: u16, request_handler: Arc<dyn RequestHandler>) -> 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 <https://github.com/asomers/mockall/issues/571>
impl RpcServer for MockRpcServerImpl {
async fn register_endpoint(
&self,
origin_filter: Option<&UUri>,
resource_id: u16,
request_handler: Arc<dyn RequestHandler>,
) -> 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<dyn RequestHandler>,
) -> Result<(), RegistrationError> {
self.do_unregister_endpoint(origin_filter, resource_id, request_handler)
.await
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;
Expand Down
12 changes: 9 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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};
Expand All @@ -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;

Expand Down
14 changes: 6 additions & 8 deletions src/utransport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -273,21 +271,21 @@ 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 <https://github.com/asomers/mockall/issues/571>
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<dyn UListener>) -> Result<(), UStatus>;
pub async fn do_unregister_listener<'a>(&'a self, source_filter: &'a UUri, sink_filter: Option<&'a UUri>, listener: Arc<dyn UListener>) -> Result<(), UStatus>;
}
}

#[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 <https://github.com/asomers/mockall/issues/571>
impl UTransport for MockTransport {
async fn send(&self, message: UMessage) -> Result<(), UStatus> {
self.do_send(message).await
Expand Down

0 comments on commit 11c652f

Please sign in to comment.