How do I test Signals (signal / computed / effect)
Summary
TLDRThis video introduces Angular's new feature, Signals, which was released as a developer preview in version 16 and has since been enhanced with exclusive features such as local change detection and signal inputs by version 17.1. The video delves into the practical integration and testing of signals within Angular applications, focusing on a case study of a basket component to demonstrate how signals, computed functions, and effects can be utilized to manage state changes and synchronize services. The presenter, an Angular Architects trainer and consultant, shares insights on testing components and services, emphasizing the importance of understanding signal dependencies and the role of change detection in triggering effects. This guide is invaluable for developers looking to leverage Angular Signals in their projects.
Takeaways
- 🚀 Angular introduced Signals as a developer preview in version 16, with version 17.1 adding exclusive features like local change detection and signal inputs.
- 🔒 By version 17, the signal function and computed function became stable, while the effect function remained in developer preview, offering new advantages for application integration.
- 🧪 Testing components using signals in Angular requires understanding of lifecycle hooks, change detection, and the effects of computed and effect functions on the component's state.
- 🔧 The Basket Component example demonstrates handling of products with signals for dynamic price calculation, employing services for synchronization and utilizing computed for total price calculation.
- 👩💻 Component testing involves interacting with the DOM through TestBed to instantiate the component, trigger events, and verify outcomes without directly accessing component instance variables.
- ⚙️ Effects triggered by signals depend on change detection to execute, necessitating understanding of how Angular processes changes and updates the DOM based on signal state.
- 📌 Testing effects within components differs from service testing due to the absence of DOM interactions and change detection, requiring manual triggering of effects through test utilities.
- 🔄 Refactoring components by extracting logic into services can streamline testing and improve code organization, while still allowing for comprehensive component behavior verification.
- 🧼 For service testing without change detection, the `flushEffects` utility is essential for manually triggering effects that are dependent on signal changes, ensuring accurate test results.
- 📚 The script underscores the importance of understanding Angular's signal-based architecture for effective testing, highlighting strategies for both component and service-level tests.
Q & A
What version of Angular introduced signals as a developer preview?
-Signals were introduced in Angular version 16 as a developer preview.
By Angular version 17.1, which features of signals became stable?
-By Angular version 17.1, the signal function and the computed function became stable.
Which function related to Angular signals is still in developer preview as of version 17.1?
-As of version 17.1, the effect function related to Angular signals is still in developer preview.
What is the purpose of the effect function in the context of Angular signals?
-The effect function is used to execute some kind of synchronization or side effects whenever the dependent signals change.
How does the 'total price' feature work in the basket component example?
-The total price in the basket component example recalculates the total cost whenever the quantity of products is increased or decreased.
What is the significance of using 'fixture.detectChanges()' in Angular component testing?
-'fixture.detectChanges()' is used to initiate the Angular lifecycle hooks and update the component with the latest data, ensuring the tests reflect the current state of the component.
How is the 'flushEffects' method useful in testing Angular services with effects?
-The 'flushEffects' method triggers the execution of any dirty (changed) effects manually, which is necessary in service tests where there is no automatic change detection.
Why is it important to understand when an effect is considered 'dirty' in Angular?
-Knowing when an effect is 'dirty' is crucial because a 'dirty' effect signifies that its dependencies have changed, necessitating a re-execution of the effect to reflect the new state.
What refactoring was done to the basket component in the example?
-The logic was refactored out of the basket component and moved into a separate basket service to simplify the component and isolate the logic for testing.
Why is it necessary to test both the Angular component and service separately in the context of signals?
-Testing both the component and service separately ensures that the logic within the service functions correctly independent of the component and that the component interacts with the service as expected.
Outlines
🔍 Introduction to Angular Signals and Testing Components
Angular has introduced signals in version 16 as a developer preview, and by version 17.1, features like local change detection and signal inputs have become exclusive with signals. The signal and computed functions have become stable, while the effect function remains in developer preview. The video discusses the advantages of integrating signals into applications, the need for careful testing, and presents a case study on testing a basket component that uses signals to manage products and their total price. It covers the basics of setting up a component test, initializing the component, and testing dynamic changes in the DOM based on user interactions.
🚀 Advanced Testing Techniques with Angular Signals
The second part delves into more advanced testing scenarios, including verifying the recalculated total price upon product updates, and the execution of the sync service effect upon product changes. The narrative emphasizes the importance of triggering change detection to update the DOM and ensure that computed properties and effects reflect the current state. It also introduces the concept of effects being 'dirty' and the necessity for external triggers (like change detection) to execute effects, highlighting the intricacies of testing Angular components and services that utilize signals.
🛠️ Solving Effects Testing Challenges in Angular Services
This section explores the idea of refactoring the basket component to move logic into a separate service for cleaner architecture and easier testing. It walks through the process of injecting the basket service into the component, adjusting the template, and providing service methods for UI interactions. The emphasis shifts to testing the basket service directly, illustrating how to access services via Angular's testbed and highlighting differences in testing strategies between components and services, especially regarding computed values and effects without direct change detection.
📢 Conclusion and Invitation to Angular Testing Workshop
The video concludes with a summary of the key points discussed, emphasizing the importance of understanding and integrating Angular signals into application development and testing. The presenter, identified as R Hamp from Angular Architects, invites viewers to learn more about Angular testing through their workshop. The closing remarks encourage viewer engagement through likes, subscriptions, and comments, promising more informative content in future videos.
Mindmap
Keywords
💡Signals
💡Change Detection
💡Effects
💡Computed
💡Component Testing
💡Service Testing
💡TestBed
💡Spies
💡Flush Effects
💡Refactoring
Highlights
Signals introduced in Angular version 16 as a developer preview, with version 17.1 already featuring exclusive signal-related enhancements.
Stabilization of the signal and computed functions in Angular 17, with the effect function remaining in developer preview.
Discussion on integrating signals into applications and the importance of testing them.
Demonstration of a component test on a Basket Component using Angular signals.
Explanation of using signals for local change detection and as inputs in Angular components.
Detailed walkthrough of testing a component with signals, focusing on interaction and DOM verification.
Techniques for testing signal effects and computed properties within Angular components.
Strategies for accessing and testing Angular services that utilize signals.
Demonstration of how change detection triggers signal effects and the importance of manual triggering in tests.
Explanation of how effects depend on change detection for execution in Angular applications.
Distinction between testing components with DOM interaction and testing services using signals.
Use of TestBed and fixture for setting up Angular component tests with signals.
Approach for testing the execution of effects multiple times and ensuring correct behavior in Angular.
Refactoring a complex Angular component by moving logic to a service to simplify testing.
Introduction to the flush effects command for manually triggering effects in service tests.
Summary of key points in testing Angular applications with signals, focusing on change detection and manual effect flushing.
Offer of an Angular testing workshop by the speaker, an Angular trainer and consultant.
Transcripts
so signals have been introduced in
angular as a developer preview in
version 16 at the time of this video we
are at
17.1 and we have already exclusive
features that come with signals which is
the local change detection and the
signal inputs with 17 the signal
function the computed function both got
stable and the effect function is still
in a developer preview but nevertheless
we have already some advantages that we
can gain from them and it's time to
think about integrating into our
applications and it also means we need
to be able to test them what do we have
to be aware of are there some things
that we have to be very careful about so
these are the things that I want to
discuss here in this video okay so this
is the component that we want to test
it's basket component we have your two
different products and as you can see it
also comes with the total price
now if I click on ADD or on decrement
increase or decrement then we see that
the total is changed here as
well another way how the implementation
works is that we have here our basket
component so that's this one quite a lot
of template there but in the end in the
typescript class we have here the
product which is a signal containing all
the products which we have seen we also
have here a sync service that's a
service which should do some kind of
synchronization stuff as soon as the
products change and that's why we have
here an
effect so every time those products
change the sync service is executed with
the sync method and then we have here
our total price the total price just
calculates yeah the total price and it
is implemented in the form of a computed
as we see here and at the very end of
course we also have our decrease our
increase methods and what they do is
that they call products update and then
they just increase the amount of the
particular product or decrease it now I
want to test this so this is all based
on signals and since this is a component
we will start or we will do a simple
component test that means I need to say
test bed configure testing
module and in there I am importing
my basket component because it is a
standalone component and then I say
create component basket component again
and I store the result in the fixture so
I have now my component
fixture what we usually always have to
do in the very beginning when a
component has been created we need to
instantiate it we need to make sure that
all the life cycle hooks start and that
stuff so that's why we call here fixture
detect
changes and now we can already verify if
the total price is is calculated in the
right way so since this is a component
here I'm not going to directly access
the component instance typescript class
but I'm really interacting with the
component via the D which means I will
click on the button I will not call the
event listener method and I will also
not verify against the property against
the signal property but um against those
things which are rendered in the
and maybe we start with that one first
so I say I need to now fetch somewhere
the total price from the Dom so I use
here my fixture again I say debug
element I say query with query I have
the possibility to use spy CSS means I
can select something with the help of
the CSS selectors and then because my
total price has attached a data test ID
attribute which makes it uniquely to
select I can say yeah there is a data
test ID attribute which has the value
total and this I want to then fetch the
native element and I want to store this
into the variable called total and this
is a HTML diff
element and if everything works I can
now verify if my total the Dom element
if I'm able to type it has a text
content with the value 18 it's a string
it's of course not the number it's a
string we are here accessing the HTML so
I rerun my
test need to rerun the right test and we
see it is working and the next step now
is that I increment the first product so
I need to click on the button so let's
quickly check out what kind of button or
how we can access this button so we have
here the button increase and that's the
data test ID so again I can use the same
selector approach that I've used
before so I say yeah this is the the
same fixture debug element this time
it's button
increase and yeah I click directly on
the native element I don't need to store
it into a variable or
something if that has been done then
internally the total price should be
recalculated which means I need to
trigger now the change detection so that
my dom gets updated with the current
value that's why I say here detect
changes and then I can copy my assertion
again and I leave it with 18 because I
would expect that it's now 21 the coffee
costs €3 and it should be 21 which means
that the test now is supposed to
fail yeah which sh us so it's
21 so we see in comparison to a
component test without any signals
there's not that much difference what's
missing though is the effect so that the
the the total price is the computed and
the effect is still missing we see
already the effect in the console log
here because it is also executed two
times and it's just print the product to
our
lock so what I want to do now is that I
want to verif y that the effect function
or the function inside of the effect is
executed two times how do I do that I
need to get access to this sync service
that we've created so I say here testb
inject sync service the sync service is
provided in root so I just can say
inject and I will get the original right
out right
away so I'm going to store this here so
sync service and I will now create a spy
on it because otherwise I can't know how
often it was executed so I say I have my
spy and that's spy on on the sync
service and I say whenever the sync
method is called I want this information
to be stored in the Spy variable
Bell good so now I need to find out
those places in my code when the Spy was
executed we can already say after the
first change detection the effect is
already executed for the first time
again I will make it a little bit
different I will let the test fail first
to verify that the test is really doing
what it is supposed to do so I say here
before detect changes
spy to have been called one time and
this should not fail because the Spy
wasn't
called and it fils right so called two
called zero times now I'm moving it
after the change
detection let's rerun
it and the test is working successful so
that means that with the change
detection the effect and not just the
computed but also the effect is also
executed and this is already very
important uh to understand what happens
here because the effect and the same in
a way is true for the computer but
especially for the effect the effect has
to be triggered by somebody and at the
moment in angular because we don't have
any signal components yet that's still
the change detection so every time the
change detection is executed it also
goes to the effect and says under the
condition that you are dirty please
rerun your inner
function what does mean dirty dirty
means that the effect is is aware of all
its depending signals and it knows at
any point in time if one of these
depending or dependent signals have been
changed but it's not executing the side
effect right away it relates that
somebody comes to it in a way and says
yeah now it's time rerun if you're dirty
and in our case again that's the change
detection that has a consequence the
consequence is if I'm running now the
change detection a second time against a
clean a not dirty effect then it will
not have any effect so the the the the
the function internally will not be
executed so if I would now think well
detect changes is running the effect the
second time I'm wrong so if I say here
two and I rerun the
test we will see that the test is going
to fail because it was just executed one
time that is important to to understand
and to
remember so when is it executed a second
time well when the change detection runs
yeah that's still necessary but also
when the effect became dirty and that's
when the products have changed which
means after I have clicked on this
native element
there and again I could not say okay I
put it here right after the click should
happen synchronously so the effect
execution should happen right after a
change to the signal happened but again
that's not the case somebody needs to
trigger the effect the effect of course
knows right now that the products have
been changed but it's still waiting that
somebody triggers it so I need to move
it right here now if I rerun it the test
is working good so there are some some
things you you have to remember but in
the end if you just run the changes as
you would normally run it then you don't
really face any difficulties when it
comes to the effect and to the computed
as long as you're working directly with
with the do as long as you have change
detection which is of course something
you don't have if you test a
service which is what we're going to do
now so I have now the idea that I say
yeah the component the basket component
here that's quite huge so it has already
quite a lot of template and also a
little bit of logic I want to move the
logic into a separate service so I'm
going to take all of that code I'm going
to copy it or cut it out we have here
already our basket
service I put it in there and so what
our component has to do now is that it
only needs to inject this basket service
so it says here inject basket service
and then of course we need to access we
need to provide expose the properties of
the basket service now for the template
so I say well there are the products and
that's the basket service so I access it
like this then we have also our total
price I also provide it like this and
then I need to provide the two functions
for increment uh increase and and and
decrease the amount of
products so I say here increase function
is a number and
please um call basket service increase
ID and then we also have here
decrease where I am doing the
opposite good well that's the
refactoring and one of the nice side
effects when you have tests is that you
can now check if the if the basket
component is still working as it is
supposed to be uh so I need to rerun the
test and we see it screen so didn't
introduce any Buck basket component is
still working but now I say okay basket
component is one thing I want to test
only the basket component H sorry the
basket
service so I go into my into back into
my test and I say it should test the
basket
service now because I don't work now
against the component I don't need to
say create component I will also not get
my component fixture I will also
therefore not get access to change
detection um so I need to find another
way but in order to get access to the
basket service it's very easy I just
have to say tpad inject inject basket
service basket service is provided in
root it has just one dependency to the
sync service which is also provided in
rout so I don't need to do anything else
in except seeing here inject and I'm
getting access to the original
services so basket service
here and then basket Service uh sync
service
there so and now let's start with the
total price first the easy one that
computed when the basket service is
instantiated that means that obviously
the total price
also needs to have already a value so
that means if I say here expect basket
service and I access the total price and
I access it by requesting the current
value then I can say well it should be
18 yeah let's say 21 because I want to
see the test
failing so I rerun
it and the test is failing because it's
18 great
so I say here 18 good now I say I want
to increase it so I say basket service
increase
one and again I hope that the test is
now failing because it should be 21 and
not
18 let's rerun
it yeah fine it's 21 good nothing really
spectacular nothing really new that just
works again why is there something we
have to be aware of
kind of as I said or as explained before
the computed works in the same way as
the effect it needs to be triggered by
somebody and otherwise I mean we can't
really say it's it's not running because
there is no side effect so in general
when it comes to computed signals it's
very easy to test them because you just
need to call them and you get the
current
value but for signals it's different so
if sorry for for for for the effect it's
different because if I now come up here
with a spy again and if I
say spy on the sync service and I'm now
um going to the to the sync method again
and I say well let's start let let's go
to the very end and we know that since
the computed was also executed well the
effect should also have been executed so
my spy should have been called Two Times
by
now let's
see no wasn't run at all not good so the
effect was never
executed and again that's the main
that's the problem here because there is
no change action there is no one there
which triggers it unfortunately we also
don't have the possibility like with
computed that we can access that effect
on our own there is no possibility to
call an effect as it is with computed
so it's not very easy but there is of
course a solution because otherwise I
wouldn't have done the video and the
solution here is flush effects so that's
a test that's a method which is provided
to you via the test bed and that is
doing exactly what the change detection
is doing for the effect namely it says
if the effect is dirty run
it but of course I need to know when to
call this command I can't just call it
somewhere the effect needs to be
dirty so I can start from the very
beginning so I say here um not the flush
effects that's the that's the name of
the command and since total price was
already already had already did the the
value 18 um with flash effects the same
is true also for the effect it is also
dirty in the very beginning so if I say
here flush it should have been executed
one
time I can rerun it
test working and again the same rules
that we have seen before with the change
detection apply here as well it needs to
be a dirty effect just running flush
effect doesn't is not enough so if I say
here again flush effect a second time
and I would expect that now the Spy has
also been executed a second time that's
not the case it's just called one
time good so I need to place the test B
flush effects for the second time where
it belongs to and that's right after the
increase after the product signal was
changed and then it will work so if I
then move the to have been called here
um also after the flash effects we will
see that now the test is working as
well so summary as long as we are
writing tests that interact with the Dom
where change detection is included we
don't see any real difference when it
comes to signals or maybe to be more
precise with effects but if you but if
we're testing services where we don't
have a change detection then we need to
flush the effects manually and that's
why the flush effects command and we
also have to remember flush effects only
works if the effect is dirty otherwise
it has no
effect by the way you can also use the
flush effect in the D base so-called
tests as well that would also work here
instead of running fixture detect
changes but yeah I mean if you have
already a Dom based test where you have
already to run the tange detection why
do you actually want to use flush
effects that doesn't make sense so that
was it already by the way my name is R
Hamp I'm trainer and consultant at
angular Architects and if you're
interested if you want to learn more
about testing in angular and maybe our
testing Workshop is exactly what you're
looking
for I would say thank you very much for
watching if you like this video please
give it a like also subscribe to my
channel and of course if you have any
questions then just ask thanks again and
see you in the next video goodbye
5.0 / 5 (0 votes)