Testing Deferrable Views with Angular Testing Library
The Angular 17 release is stacked with new features, using Vite and esbuild to build applications, signals, control flow, and deferrable views, just to name a few big ones. It's the last one, deferrable views, that I want to go over through because it impacts component tests.
Deferrable views, also known as @defer blocks, are a powerful tool that can be used to reduce the initial bundle size of your application or defer heavy components that may not ever be loaded until a later time. This should result in a faster initial load and an improvement in your Core Web Vitals (CWV) results. Deferring some of your components until later should specifically improve Largest Contentful Paint (LCP) and Time to First Byte (TTFB).
Testing Deferrable Views link
What I like about the (new!) Angular documentation is that it has an example on how to test deferrable views.
In the example, we see the following component, which makes use of a complete block (@defer
), and a loading block (@loading
).
To test the deferrable blocks of the component, you'll need to use the new fixture.getDeferBlocks()
and deferBlockFixture.render(DeferBlockState)
methods.
This is to render the view within its correct state. In other words, within the test have total control over the current deferrable state of the block(s) in your component.
By just rendering the component, the body is empty.
If the component was using a placeholder view (using @placeholder
), then this will be rendered in this state.
As expected, setting the defer block to the loading state renders the loading block.
Lastly, updating the defer state to complete, replaces the loading content with the completed content.
Using Angular Testing Library link
The same test can be rewritten using the Angular Testing Library API.
Using the component's fixture, which is returned from the render
method, the newly provided methods can be invoked to get a deferrable block and to set its state.
What changed in the above example using the Testing Library API and the original "default" test is that we make use of:
- the
render
method to render the component; - the queries method of
screen
to get specific elements within the DOM; - and lastly the assertions from
jest-dom
to make the assertions better readable and clear;
Following the Testing Library philosophy, we test a component as a real user. This results in more robust tests, and usually also has the benefit that tests are easier to write and to read.
But, we can do better. I don't like that we have to take a few steps in order to put the component in a desired state, which probably will also be repeated in many tests. The test cases can quickly become bloated.
(Angular) Testing Library aims to make writing tests fun. We can only achieve this goal with the right abstractions, to keep the test cases as simple as possible. That's why we added two new APIs to make the interaction with deferrable views less verbose.
renderDeferBlock
link
The first one is the renderDeferBlock
method sets a deferrable block in the given state.
This is just a simple wrapper to get and set the state.
Underneath it makes use of the getDeferBlocks
and render(DeferBlockState)
methods, so nothing special going on here.
The default behavior sets the state of all deferrable blocks within the component.
To explicitly set the state of a specific block, you can pass the index of the deferrable block you want to update to renderDeferBlock
.
deferBlockStates
link
Besides setting the deferred state within a test, Angular Testing Library provides an option to immediately render the component with its deferrable blocks in the desired state.
Use the deferBlockStates
configuration option while rendering the component.
Just like before, we can also be explicit about it and set the desired state of specific blocks.
Conclusion link
Testing deferrable views can be done by setting the state (Placeholder
, Loading
, Complete
, Error
) of a deferrable block.
While Angular already provides some utility methods, Angular Testing Library (v14.5.0) ships two easy-to-use abstractions to make working with defferable views easier within tests.
I see myself using the new deferBlockStates
more frequently, to set the initial state of a block.
This keeps the tests focused around a particular state, than instead having to update the state of a component (multiple times) within a test.
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.