tonic_mock/test_utils.rs
1/*!
2# Test Utilities
3
4This module provides helper types and functions to simplify testing gRPC services
5with streaming interfaces. It is enabled by default but can be disabled by setting
6`default-features = false` when including the crate.
7
8## Core Components
9
10- [`TestRequest`]: A simple request message type for testing
11- [`TestResponse`]: A simple response message type for testing
12- [`create_test_messages`]: Create a vector of test messages with sequential IDs
13- [`create_stream_response`]: Create a streaming response from a vector of messages
14- [`create_stream_response_with_errors`]: Create a streaming response with errors at specified indices
15- [`assert_message_eq`]: Assert that a message matches expected values
16- [`assert_response_eq`]: Assert that a response matches expected values
17
18## Example Usage
19
20```rust
21use tonic::{Request, Response, Status, Code};
22use tonic_mock::{streaming_request, process_streaming_response};
23use tonic_mock::test_utils::{
24 TestRequest, TestResponse, create_test_messages,
25 create_stream_response, assert_response_eq
26};
27
28// Create test messages
29let messages = create_test_messages(5);
30assert_eq!(messages.len(), 5);
31
32// Create a streaming request
33let request = streaming_request(messages);
34
35// Call your service (or mock it for this example)
36let responses = vec![
37 TestResponse::new(200, "OK: 0"),
38 TestResponse::new(200, "OK: 1"),
39 TestResponse::new(200, "OK: 2"),
40 TestResponse::new(200, "OK: 3"),
41 TestResponse::new(200, "OK: 4"),
42];
43let response = create_stream_response(responses);
44
45// Process the streaming response
46process_streaming_response(response, |result, index| {
47 assert!(result.is_ok());
48 let response = result.unwrap();
49 assert_response_eq(&response, 200, format!("OK: {}", index));
50}).await;
51
52// Test error handling
53let responses = vec![
54 TestResponse::new(200, "OK: 0"),
55 TestResponse::new(200, "OK: 1"),
56 TestResponse::new(200, "OK: 2"),
57];
58let error_status = Status::new(Code::Internal, "Simulated error");
59let response = create_stream_response_with_errors(
60 responses,
61 vec![1], // Error at index 1
62 error_status
63);
64
65// Process response with errors
66process_streaming_response(response, |result, index| {
67 match index {
68 1 => {
69 assert!(result.is_err());
70 assert_eq!(result.unwrap_err().code(), Code::Internal);
71 },
72 _ => {
73 assert!(result.is_ok());
74 let response = result.unwrap();
75 assert_eq!(response.code, 200);
76 }
77 }
78}).await;
79```
80
81These utilities make it easier to test gRPC streaming services by providing
82ready-to-use message types and helper functions for common testing patterns.
83*/
84
85use crate::StreamResponseInner;
86use bytes::Bytes;
87use prost::Message;
88use tonic::{Response, Status};
89
90/// Test request message for use in gRPC service tests
91///
92/// This provides a simple message type that implements the required traits
93/// for use with tonic and can be used for testing streaming requests.
94#[derive(Clone, PartialEq, Message)]
95pub struct TestRequest {
96 #[prost(bytes = "bytes", tag = "1")]
97 pub id: Bytes,
98 #[prost(bytes = "bytes", tag = "2")]
99 pub data: Bytes,
100}
101
102impl TestRequest {
103 /// Create a new test request with the given ID and data
104 pub fn new(id: impl Into<Bytes>, data: impl Into<Bytes>) -> Self {
105 Self {
106 id: id.into(),
107 data: data.into(),
108 }
109 }
110}
111
112/// Test response message for use in gRPC service tests
113///
114/// This provides a simple response type that can be used for testing
115/// streaming responses from gRPC services.
116#[derive(Clone, PartialEq, Message)]
117pub struct TestResponse {
118 #[prost(int32, tag = "1")]
119 pub code: i32,
120 #[prost(string, tag = "2")]
121 pub message: String,
122}
123
124impl TestResponse {
125 /// Create a new test response with the given code and message
126 pub fn new(code: i32, message: impl Into<String>) -> Self {
127 Self {
128 code,
129 message: message.into(),
130 }
131 }
132}
133
134/// Create a vector of test messages with sequential IDs
135///
136/// This is useful for generating a batch of test messages to use
137/// with the streaming_request function.
138///
139/// # Example
140/// ```
141/// # use tonic_mock::test_utils::{create_test_messages, TestRequest};
142/// let messages = create_test_messages(5);
143/// assert_eq!(messages.len(), 5);
144/// ```
145pub fn create_test_messages(count: usize) -> Vec<TestRequest> {
146 let mut messages = Vec::with_capacity(count);
147 for i in 0..count {
148 messages.push(TestRequest::new(i.to_string(), format!("test_data_{}", i)));
149 }
150 messages
151}
152
153/// Create a streaming response from a vector of response messages
154///
155/// This is useful for simulating streaming responses in test code.
156///
157/// # Example
158/// ```
159/// # use tonic_mock::test_utils::{create_stream_response, TestResponse};
160/// let responses = vec![
161/// TestResponse::new(0, "Response 0"),
162/// TestResponse::new(1, "Response 1"),
163/// ];
164/// let stream_response = create_stream_response(responses);
165/// ```
166pub fn create_stream_response<T>(responses: Vec<T>) -> Response<StreamResponseInner<T>>
167where
168 T: Clone + Send + Sync + 'static,
169{
170 #[cfg(feature = "test-utils")]
171 {
172 let stream = async_stream::try_stream! {
173 for response in responses {
174 yield response;
175 }
176 };
177
178 Response::new(Box::pin(stream))
179 }
180
181 #[cfg(not(feature = "test-utils"))]
182 {
183 unimplemented!("This function requires the test-utils feature")
184 }
185}
186
187/// Create a streaming response with errors at specified indices
188///
189/// This is useful for testing error handling in code that processes
190/// streaming responses.
191///
192/// # Example
193/// ```
194/// # use tonic_mock::test_utils::{create_stream_response_with_errors, TestResponse};
195/// # use tonic::{Status, Code};
196/// let responses = vec![
197/// TestResponse::new(0, "Response 0"),
198/// TestResponse::new(1, "Response 1"),
199/// TestResponse::new(2, "Response 2"),
200/// ];
201/// let error_status = Status::new(Code::Internal, "Test error");
202/// let stream_response = create_stream_response_with_errors(
203/// responses,
204/// vec![1],
205/// error_status
206/// );
207/// ```
208pub fn create_stream_response_with_errors<T>(
209 responses: Vec<T>,
210 error_indices: Vec<usize>,
211 error_status: Status,
212) -> Response<StreamResponseInner<T>>
213where
214 T: Clone + Send + Sync + 'static,
215{
216 #[cfg(feature = "test-utils")]
217 {
218 let stream = async_stream::try_stream! {
219 for (i, response) in responses.into_iter().enumerate() {
220 if error_indices.contains(&i) {
221 yield Err(error_status.clone())?;
222 } else {
223 yield response;
224 }
225 }
226 };
227
228 Response::new(Box::pin(stream))
229 }
230
231 #[cfg(not(feature = "test-utils"))]
232 {
233 unimplemented!("This function requires the test-utils feature")
234 }
235}
236
237/// Assert that a test message matches the expected ID and data
238///
239/// This is a convenience function for testing that a message's content
240/// matches the expected values.
241///
242/// # Example
243/// ```
244/// # use tonic_mock::test_utils::{assert_message_eq, TestRequest};
245/// let message = TestRequest::new("test_id", "test_data");
246/// assert_message_eq(&message, "test_id", "test_data");
247/// ```
248pub fn assert_message_eq(message: &TestRequest, id: impl AsRef<str>, data: impl AsRef<str>) {
249 let id_bytes = Bytes::from(id.as_ref().to_string());
250 let data_bytes = Bytes::from(data.as_ref().to_string());
251 assert_eq!(message.id, id_bytes);
252 assert_eq!(message.data, data_bytes);
253}
254
255/// Assert that a test response matches the expected code and message
256///
257/// This is a convenience function for testing that a response's content
258/// matches the expected values.
259///
260/// # Example
261/// ```
262/// # use tonic_mock::test_utils::{assert_response_eq, TestResponse};
263/// let response = TestResponse::new(200, "OK");
264/// assert_response_eq(&response, 200, "OK");
265/// ```
266pub fn assert_response_eq(response: &TestResponse, code: i32, message: impl AsRef<str>) {
267 assert_eq!(response.code, code);
268 assert_eq!(response.message, message.as_ref());
269}