Introduce a crate to write unit tests of Dioxus apps#5323
Introduce a crate to write unit tests of Dioxus apps#5323hovinen wants to merge 6 commits intoDioxusLabs:mainfrom
Conversation
To be turned into prose:
- Existing testing options are dioxus-ssr and Playwright
- dioxus-ssr does not allow interaction with the DOM in a test. As soon
as the test needs to click a button, it won't work.
- Playwright is extremely heavy-weight. Most importantly, it creates a
technological divide between the test code and the component under
test. This makes it really hard to instrument the component under
test from the test itself. Writing new tests is a major undertaking.
- I want it to be easy to write new tests. And the tests should execute
very quickly in the normal case.
- Inspired by testing libraries in Flutter and React
- Based on dioxus-native and blitz
- Test creates a `Tester` by rendering into it. It can then query
elements to obtain `TestElement` instances which reference the DOM.
They can dispatch events via methods on those functions.
- For async and handling events: call `Tester::pump`. This awaits so that
it passes control back to the runtime to handle any other async tasks.
- For interaction: events dispatched through the VirtualDom
- This requires a change to dioxus-native: we need to be able to
construct synthetic events. Previously, visibility restrictions
prevented that. This adds a function to generate a synthetic click
analogous to the function in Blitz.
- In this PR only "click" is supported. Further events can be added but I
would like feedback before fleshing it out.
- Since events go through the VirtualDom, they hit the target element
directly. If the target element is covered by a frost or is otherwise
inaccessible, then the click will have no effect in reality but will
still work in the test.
- The same might be true of disabled elements.
- Writing a new renderer
- Seems redundant since the renderer in dioxus-native serves the needs
of this library perfectly.
- Dispatching events via `DioxusDocument::handle_ui_event`:
- Layout with Blitz does not work reliably enough
- There is no "click" event variant in the `UiEvent` enum. So this
would require a change in Blitz as well.
| timeout(PUMP_TIMEOUT, self.document.vdom.wait_for_work()).await?; | ||
| while self.document.poll(None) {} | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Very excited about this, thanks for working on it!
Instead of requiring explicit event pumping, we could mimic the Playwright test API and make queries asynchronously run the event loop:
let mut tester = dioxus_test::render(AComponent).build();
// Wait for make-request-button to load and click it to make a network request.
tester.find_by_test_id("make-request-button").click().await?;
// Assert on the state of the UI while the network request is in flight.
expect(tester.find_by_test_id("loading-indicator")).to_be_visible().await?;
// Receive the server response and assert on the state of the UI after the response
// is received and the UI has been rerendered.
expect(tester.find_by_test_id("resolved-content")).to_be_visible().await?;In some cases, waiting for work and applying a single tick of DOM operations could provide more control over testing, but I think the familiarity of the Playwright-style API and intuitive behavior in the presence of feedback loops with DOM events are typically more important.
For example, onmounted and use_effect will only trigger after the document has applied edits. With the current model, I think that would require rendering, then waiting for a 0ms timeout, then asserting the state has changed based on the onmounted event. If asserting on that state also pumped the DOM, you could just assert the state is correct directly.
There was a problem hiding this comment.
Thanks -- let me try this and see what I can come up with.
This is a preliminary version of a library in the spirit of React or Flutter to allow easy unit testing of Dioxus apps.
Right now it only supports the
clickevent, but I believe that is sufficient to show the design. Once the overall design is finalised, it can be fully fleshed out.I placed this directly in the Dioxus repository. I'm also fine with keeping it as a separate crate if desired. However, this requires a few small changes to the dioxus-native crate to allow the creation of synthetic events. Otherwise the existing visibility restrictions make it impossible to dispatch DOM events from the test.
I decided to add fairly fleshed out documentation including doctests so as to illustrate the use of the library. I have tested it against a personal project and it is working quite well.
Alternatives considered
Document::handle_ui_event. This would mean that the events would act more like real user-generated events, where, say, a click happens at particular coordinates and the framework identifies the element which is hit. This would be more "realistic" in the sense that a click on a button which is covered by another element would not hit the button but rather the element covering it. However, theUiEventenum from Blitz does not support a "click" event and I did not want to create multiple PRs in different repositories for now.Fixes #5324