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}