Skip to content

Commit db12777

Browse files
feat(core): allow the MockRuntime to call a command and get a return value + fix doctest (#8380)
* Add `get_ipc_response` to the `MockRuntime` * Fix `MockRuntime` doctests * Add support for raw ipc * cleanup, add change file * only a single get fn --------- Co-authored-by: Lucas Nogueira <lucas@crabnebula.dev>
1 parent 5848b4e commit db12777

File tree

2 files changed

+115
-96
lines changed

2 files changed

+115
-96
lines changed

.changes/get-ipc-response-test.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:enhance
3+
---
4+
5+
Added `test::get_ipc_response`.

core/tauri/src/test/mod.rs

+110-96
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,38 @@
1111
//! # Examples
1212
//!
1313
//! ```rust
14+
//! use tauri::test::{mock_builder, mock_context, noop_assets};
15+
//!
1416
//! #[tauri::command]
15-
//! fn my_cmd() {}
17+
//! fn ping() -> &'static str {
18+
//! "pong"
19+
//! }
1620
//!
17-
//! fn create_app<R: tauri::Runtime>(mut builder: tauri::Builder<R>) -> tauri::App<R> {
18-
//! builder
19-
//! .setup(|app| {
20-
//! // do something
21-
//! Ok(())
22-
//! })
23-
//! .invoke_handler(tauri::generate_handler![my_cmd])
24-
//! // remove the string argument on your app
25-
//! .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
26-
//! .expect("failed to build app")
21+
//! fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
22+
//! builder
23+
//! .invoke_handler(tauri::generate_handler![ping])
24+
//! // remove the string argument to use your app's config file
25+
//! .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
26+
//! .expect("failed to build app")
2727
//! }
2828
//!
2929
//! fn main() {
30-
//! // let app = create_app(tauri::Builder::default());
31-
//! // app.run(|_handle, _event| {});
32-
//! }
30+
//! let app = create_app(mock_builder());
31+
//! let window = tauri::WindowBuilder::new(&app, "main", Default::default())
32+
//! .build()
33+
//! .unwrap();
3334
//!
34-
//! //#[cfg(test)]
35-
//! mod tests {
36-
//! use tauri::Manager;
37-
//! //#[cfg(test)]
38-
//! fn something() {
39-
//! let app = super::create_app(tauri::test::mock_builder());
40-
//! let window = app.get_window("main").unwrap();
41-
//! // do something with the app and window
42-
//! // in this case we'll run the my_cmd command with no arguments
43-
//! tauri::test::assert_ipc_response(
44-
//! &window,
45-
//! tauri::window::InvokeRequest {
46-
//! cmd: "my_cmd".into(),
47-
//! callback: tauri::ipc::CallbackFn(0),
48-
//! error: tauri::ipc::CallbackFn(1),
49-
//! body: serde_json::Value::Null.into(),
50-
//! headers: Default::default(),
51-
//! },
52-
//! Ok(())
53-
//! );
54-
//! }
35+
//! // run the `ping` command and assert it returns `pong`
36+
//! let res = tauri::test::get_ipc_response(
37+
//! &window,
38+
//! tauri::window::InvokeRequest {
39+
//! cmd: "ping".into(),
40+
//! callback: tauri::ipc::CallbackFn(0),
41+
//! error: tauri::ipc::CallbackFn(1),
42+
//! body: tauri::ipc::InvokeBody::default(),
43+
//! headers: Default::default(),
44+
//! },
45+
//! ).map(|b| b.deserialize::<String>().unwrap());
5546
//! }
5647
//! ```
5748
@@ -61,14 +52,10 @@ mod mock_runtime;
6152
pub use mock_runtime::*;
6253
use serde::Serialize;
6354

64-
use std::{
65-
borrow::Cow,
66-
fmt::Debug,
67-
hash::{Hash, Hasher},
68-
};
55+
use std::{borrow::Cow, fmt::Debug};
6956

7057
use crate::{
71-
ipc::{CallbackFn, InvokeResponse},
58+
ipc::{InvokeBody, InvokeError, InvokeResponse},
7259
window::InvokeRequest,
7360
App, Builder, Context, Pattern, Window,
7461
};
@@ -77,19 +64,6 @@ use tauri_utils::{
7764
config::{Config, PatternKind, TauriConfig},
7865
};
7966

80-
#[derive(Eq, PartialEq)]
81-
struct IpcKey {
82-
callback: CallbackFn,
83-
error: CallbackFn,
84-
}
85-
86-
impl Hash for IpcKey {
87-
fn hash<H: Hasher>(&self, state: &mut H) {
88-
self.callback.0.hash(state);
89-
self.error.0.hash(state);
90-
}
91-
}
92-
9367
/// An empty [`Assets`] implementation.
9468
pub struct NoopAsset {
9569
csp_hashes: Vec<CspHash<'static>>,
@@ -175,74 +149,114 @@ pub fn mock_app() -> App<MockRuntime> {
175149
/// # Examples
176150
///
177151
/// ```rust
152+
/// use tauri::test::{mock_builder, mock_context, noop_assets};
153+
///
178154
/// #[tauri::command]
179155
/// fn ping() -> &'static str {
180-
/// "pong"
156+
/// "pong"
181157
/// }
182158
///
183-
/// fn create_app<R: tauri::Runtime>(mut builder: tauri::Builder<R>) -> tauri::App<R> {
184-
/// builder
185-
/// .invoke_handler(tauri::generate_handler![ping])
186-
/// // remove the string argument on your app
187-
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
188-
/// .expect("failed to build app")
159+
/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
160+
/// builder
161+
/// .invoke_handler(tauri::generate_handler![ping])
162+
/// // remove the string argument to use your app's config file
163+
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
164+
/// .expect("failed to build app")
189165
/// }
190166
///
191167
/// fn main() {
192-
/// // let app = create_app(tauri::Builder::default());
193-
/// // app.run(|_handle, _event| {});}
194-
/// }
195-
///
196-
/// //#[cfg(test)]
197-
/// mod tests {
198-
/// use tauri::Manager;
199-
///
200-
/// //#[cfg(test)]
201-
/// fn something() {
202-
/// let app = super::create_app(tauri::test::mock_builder());
203-
/// let window = app.get_window("main").unwrap();
168+
/// let app = create_app(mock_builder());
169+
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
170+
/// .build()
171+
/// .unwrap();
204172
///
205173
/// // run the `ping` command and assert it returns `pong`
206174
/// tauri::test::assert_ipc_response(
207-
/// &window,
208-
/// tauri::window::InvokeRequest {
209-
/// cmd: "ping".into(),
210-
/// callback: tauri::ipc::CallbackFn(0),
211-
/// error: tauri::ipc::CallbackFn(1),
212-
/// body: serde_json::Value::Null.into(),
213-
/// headers: Default::default(),
214-
/// },
215-
/// // the expected response is a success with the "pong" payload
216-
/// // we could also use Err("error message") here to ensure the command failed
175+
/// &window,
176+
/// tauri::window::InvokeRequest {
177+
/// cmd: "ping".into(),
178+
/// callback: tauri::ipc::CallbackFn(0),
179+
/// error: tauri::ipc::CallbackFn(1),
180+
/// body: tauri::ipc::InvokeBody::default(),
181+
/// headers: Default::default(),
182+
/// },
217183
/// Ok("pong")
218184
/// );
219-
/// }
220185
/// }
221186
/// ```
222187
pub fn assert_ipc_response<T: Serialize + Debug + Send + Sync + 'static>(
223188
window: &Window<MockRuntime>,
224189
request: InvokeRequest,
225190
expected: Result<T, T>,
226191
) {
192+
let response =
193+
get_ipc_response(window, request).map(|b| b.deserialize::<serde_json::Value>().unwrap());
194+
assert_eq!(
195+
response,
196+
expected
197+
.map(|e| serde_json::to_value(e).unwrap())
198+
.map_err(|e| serde_json::to_value(e).unwrap())
199+
);
200+
}
201+
202+
/// Executes the given IPC message and get the return value.
203+
///
204+
/// # Examples
205+
///
206+
/// ```rust
207+
/// use tauri::test::{mock_builder, mock_context, noop_assets};
208+
///
209+
/// #[tauri::command]
210+
/// fn ping() -> &'static str {
211+
/// "pong"
212+
/// }
213+
///
214+
/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {
215+
/// builder
216+
/// .invoke_handler(tauri::generate_handler![ping])
217+
/// // remove the string argument to use your app's config file
218+
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
219+
/// .expect("failed to build app")
220+
/// }
221+
///
222+
/// fn main() {
223+
/// let app = create_app(mock_builder());
224+
/// let window = tauri::WindowBuilder::new(&app, "main", Default::default())
225+
/// .build()
226+
/// .unwrap();
227+
///
228+
/// // run the `ping` command and assert it returns `pong`
229+
/// let res = tauri::test::get_ipc_response(
230+
/// &window,
231+
/// tauri::window::InvokeRequest {
232+
/// cmd: "ping".into(),
233+
/// callback: tauri::ipc::CallbackFn(0),
234+
/// error: tauri::ipc::CallbackFn(1),
235+
/// body: tauri::ipc::InvokeBody::default(),
236+
/// headers: Default::default(),
237+
/// },
238+
/// );
239+
/// assert!(res.is_ok());
240+
/// assert_eq!(res.unwrap().deserialize::<String>().unwrap(), String::from("pong"));
241+
/// }
242+
///```
243+
pub fn get_ipc_response(
244+
window: &Window<MockRuntime>,
245+
request: InvokeRequest,
246+
) -> Result<InvokeBody, serde_json::Value> {
227247
let (tx, rx) = std::sync::mpsc::sync_channel(1);
228248
window.clone().on_message(
229249
request,
230250
Box::new(move |_window, _cmd, response, _callback, _error| {
231-
assert_eq!(
232-
match response {
233-
InvokeResponse::Ok(b) => Ok(b.into_json()),
234-
InvokeResponse::Err(e) => Err(e.0),
235-
},
236-
expected
237-
.map(|e| serde_json::to_value(e).unwrap())
238-
.map_err(|e| serde_json::to_value(e).unwrap())
239-
);
240-
241-
tx.send(()).unwrap();
251+
tx.send(response).unwrap();
242252
}),
243253
);
244254

245-
rx.recv().unwrap();
255+
let res = rx.recv().expect("Failed to receive result from command");
256+
match res {
257+
InvokeResponse::Ok(b) => Ok(b),
258+
InvokeResponse::Err(InvokeError(v)) => Err(v),
259+
}
246260
}
247261

248262
#[cfg(test)]

0 commit comments

Comments
 (0)