- In React, a functional component
can store and modify state during its lifetime.
For example, it could have a hit count
and every time the user click the button
it could increment the hit count.
So the component would need to store the value somewhere
and the thing is it couldn't just store that value
in a local variable inside the function
because every time the function ends
that local variable will be lost.
So, functional components need somewhere else
where they can store persistent data
and these days in React
we have this mechanism called a state hook.
Basically you hand the value over to React
and React stores the value permanently
whilst the component is alive.
So to use state hooks, we have a useState function.
You call this function in your component
when you are first rendered.
When you call that function
you give it the initial value
that you would like for your variable
and then the useState function what it does it grabs.
Imagine that this kind of blob here, is React memory.
When you call the useState function,
React allocates a chunk of memory in there,
whatever the initial value is
it puts that value into there.
Let's say it's zero.
Okay, like that.
And then what the function returns back
it returns back two values.
It actually returns back an array of two outputs.
So we have the first element in array,
I've called it stateVar.
Basically that's the current value
of your state variable, so it's zero initially.
The next element in the array
or the next item it returns back, updateFunc,
I've put it into a variable called updateFunc.
That's interesting.
That's a pointer to a callback function provided by React.
React has a callback function to say
if you ever need to change the value, call this function
and it gives you a pointer to that function.
So whenever you wanna change the value
you've gotta call that function with the new value.
So that's what you get back when you call React.useState.
So if you wanted to display the value of your variable
you can just use stateVar.
So stateVar, that's the current value of the variable,
you can just display that on your webpage.
If you've had to change the value,
then you have to call the update function.
You call the update function
and you pass in the new value.
So let's say it's one.
React would store the new value
in it's internal memory, like that.
So the value is now going to be one.
And also because React realizes
that you've just changed the state,
React will automatically re-render your component
with the new value.
That's why it's important that you call the function.
By calling the function you're notifying React
that the value has changed
so that it can re-render your component
with the new value.
So that's great.
That mechanism is basically how you store values
in a kind of semi-permanent basis in a React component.
The example we're gonna look at
is gonna see how to test that mechanism.
How can I verify that my component
does update the value when it should
based on a new user for the minimum text box.
So for example, the Enzyme_State folder in lesson 10,
I've got a demo application,
it's similar to the application we've seen before.
It has a ProductList component,
displays a bunch of products.
There are a couple of text boxes
which allow the user to specify
a minimum and a maximum value.
My component stores those values in state
and then when the user types in a new value in a text box,
it updates those values.
It updates the value of min and max in React state
and then that causes the component to be re-rendered.
Automatically whenever you change state in a React component
React will automatically refresh your component.
It'll re-render it with a filtered list of products.
So the demo application,
I'm just gonna run it quickly just to remind you
and here it is.
If you go into the Enzyme_State demo app folder,
npm-install, npm-run or npm-start,
and then we've got here all products displayed initially.
Let's set the minimum price to be 300.
Let's set the maximum price to be 500.
So there you can see, as we type values in, it works.
What I'm gonna do now is to test the code that does that.
So a quick reminder
here's the code in my ProductList component.
First thing it does
is to say I would like to store a value, the minimum value,
in React state.
This variable is the current value of minimum.
This is a pointer to a function
that I've gotta call to change it
and the same for the maximum.
The maximum value initially
or the default max is a million,
so that's the default maximum.
If I want to change the value
that's the function to invoke.
The text boxes are down below.
If I scroll down in my component, there's a text box here.
If the user types in the value, that's the minimum text box.
If the user types in the value
it calls back this function in my component
and that's up here, onChangeMin.
So when the user types in the value for the text box,
this function gets invoked.
It grabs the value entered from the user in the text box
or zero, if it was empty,
and then it calls the callback function.
So this is what I'm gonna test.
This is gonna be the purpose of my test,
simulate the user entering a value in the minimum text box
and then test that this function was invoked.
Check that the setMin function was invoked
or the setMax function was invoked,
basically I'm testing whether my component updates React
with the value entered by the user.
When the user enters the value in the text box
test that my component sends the new value to React
by calling the appropriate updated function
in React.useState.
Now then, in order to test state in Enzyme
it's a bit tricky because this state hook
is an entirely internal mechanism managed by React
and Enzyme application has no direct access
to the state mechanism.
So we have to be a bit more clever about things.
What I can do is detect
if the update state function was called,
in other words, did my code called the setMin function
or the setMax function when it should have done.
So here is the code that I'm going to use to detect
whether my update functions are invoked.
It's quite interesting.
So I'm just gonna draw a picture, first of all,
to show what happens in the normal circumstance,
and then I'll draw a picture to show
what's happening here where we are mocking things.
So under normal circumstances
when my component is rendered
it called the React.useState function,
React.useState as we've just seen,
and specified for the minimum value,
it specified an initial value of zero.
Let's see.
Okay, so we've seen that.
And what we've seen is that React internally
sets aside a bit of storage to hold that value
and what it gives me back is a variable
that will hold the initial value, as specified zero,
and also a pointer to a function
that I can invoke back again afterwards.
That's what I'd normally get back
when I call React.useState,
I get back an array.
The first element is the current value of my variable
and the second element is a callback function
that I can invoke to tell React when the value changes.
This is what I want to test.
I want to test is this function being invoked properly
when the user enters a new value in the text box.
So let's see what happens in my test.
Let's see.
So what I'm gonna do is I'm gonna define
a mock state update function.
Basically I'm gonna provide a mock version
of this function.
Because I wanna basically simulate
what's happening in React.useState,
I'm providing a mock version of that function.
This is a mock version of the callback function
that would hopefully be invoked
when the user types in a new value for the minimum.
So bear that in mind.
What's next?
Well the second thing I'm doing
is I'm actually intercepting,
I'm actually gonna provide a mock implementation
of the React.useState function in the React module.
I'm going to emulate.
I'm gonna provide a mock for the useState function.
So when my component thinks it's calling React.useState
in fact, it's not, it's going to be calling a mock,
I've got a mock or a spike.
It's gonna be calling something else instead.
I've mocked the useState function.
I've mocked the useState function,
the React.useState function
so that when it's invoked, the mock version of my function,
which I'll just kind of draw right here,
a mock version of React.useState,
it will be a simple do-nothing function
that returns an array, a mock array basically,
it'll return a mock array
with the value zero for the current value
and a pointer to the mock callback function there.
There you go.
So when my component calls React.useState,
it won't be actually calling React.useState anymore,
it'll be calling my mock version of React.useState
and what my mock version of React.useState does,
it says, if you want to change the value of the state
call this function.
So basically I've hooked up my mock setMin function here.
That is the function that my component thinks
it needs to call whenever the value changes.
The setMin function
is basically my mock function here.
The real functions that React might have provided
have been replaced by my mock implementation here.
When my component says setMin
is actually gonna call this mock object
is gonna call this mock function here
and I can check, has that function been evoked?
Did my component actually call this segment function
when the user typed in a new value in the minimum text box?
Well, that was quite tricky.
Let's put it all together.
In my test harness, I've got several tests
they're all quite similar to each other,
so I'll just focus on the first function, the first test.
You can get all the tests, if you want to, here.
I've got some dummy products,
I've got something that happens after each test,
I need to basically tidy up my mocks.
I've got four tests which are all really,
you know, evolutions of each other,
we just need to look at the first one
to understand what's going on.
So test that the state was updated
when the user entered a value in the minimum text box.
That's what we got here.
So remember, well let's actually do this
from first principles then.
So first of all, I'm setting up a dummy version
of a callback function.
That's a dummy version of the function
that the component will hopefully invoke.
Here's my component here,
which will come into play in a moment.
That's the first line.
The second line, when I basically set up
the dummy version of React.useState.
So when my component calls React.useState,
in fact, it's gonna be calling this dummy function instead
and that's what I drew on the previous slide,
a dummy version of React.useState,
and when that function is invoked
it will return a dummy set of values.
It'll return an array.
The first element is zero,
like the value of minimum,
and then the second parameter it says,
oh, call this function, call the mock function.
That's the function that will be invoked.
That's what comes back.
So in my application, let's see what happens.
When I mount my product component with products
I then simulate the user entering a value
in the minimum text box,
I'm simulating the user entering a value
in the min text box, a change event,
as if the user had entered 200 in the minimum text box.
Well, if I just go back to the actual code,
just to remind you, in my actual code for ProductList,
if the user had entered the value 200
in the minimum text box,
it would've called onChangeMin,
onChangeMin says, get the value, 200,
and invoke the callback function.
Well, that's the callback function that I've mocked
so when I simulated the user entering a value
in the text box, what I'm testing is I'm saying
did it actually call the updated function
with the value 200?
So verify that my mock implementation
of the updated function has been called
with the value 200.
I've got an important step to do as well,
after each test, mock functions accumulate history
about how many times they've been called,
what values they've been called with,
what parameters and such like.
So it's pretty important that the end of each test
that you clear any mock objects and reinstate the originals.
So when you say jest.restoreAllMocks,
it clears all information about
how those mock functions were invoked,
it forgets any implementation that you've installed
for those mock functions and return values
and it reinstates the original function.
So after each test, I will reinstate
the original React.useState function.
What I'll then do in the next test
is to mock it again with a fresh mock.
So I've got three other tests
that simulate the user clicking
the minimum text box but with an empty value
or the user entering a value in the maximum text box
or the user having an empty maximum text box.
Should we just have a look at the second one maybe?
Just as a proof of concept and see what that does.
Let's have a look.
This is the second test.
These two tests test the minimum text box
and the last two tests test the maximum text box.
They're pretty much the same idea
so let's have a look at this one.
Define a mock callback function
that my component will invoke
when it thinks it's set in the minimum price.
Define a mock implementation for React.useState
such that it will hook up my mock implementation of setMin.
That's the function my component
will actually be invoking.
So mount my component, ProductList,
simulate entry into the minimum text box,
simulating the user having an empty text box.
Now that's an interesting thing.
What value should it adopt for the minimum
if the text box is empty and I'm saying
ideally it should say it should call back
into React and set the minimum price to be zero.
So if the user has an empty text box for minimum
then that's as if the value was zero.
It should tell React to use the minimum value of zero.
But my code actually does do that,
if you remember, I was quite careful
when I grab the minimum value from the user
if the text box was empty,
it substitutes the value zero in
and it passes zero into setMin.
Okay, well, that's what I've just verified.
If the text box,
if the user enters an empty value in the text box,
an empty value in the text box,
then the minimum price should be set to zero.
And in a similar kind of way
let's have a look at what happens in the case
where it's an empty maximum box here.
So register a mock version of a callback
to update the maximum price.
Mock the React.useState function
such that it returns the value zero
and it says, call this function
if you want to change the value of the maximum.
Mount the component, ProductList,
simulate the user entering the value into the max text box,
an empty text box.
What value should it then set for the maximum price
if the maximum price is empty?
Well, what did it do if the maximum text box is empty?
What does it use for the maximum price?
Well, of course, in the real code,
the value that it uses when it tries to get the value
for the maximum price,
the value it uses by default, if it's empty,
is defaultMax, defaultMax is a million.
So if the text box is empty
my code should have called the setMax function
with a value of a million.
Did it do that?
Let's have a look.
If the maximum text box is empty,
then it should have called the setMax function
with the value of a million.
So really what I'm doing is I'm checking
is the correct default value used for the maximum
in the case where the text box was empty.
That's an interesting important test to have,
something that matters, you know?
If the text box is empty
we need to know what to do in that situation,
simulate a value of a million.
So, all what we need to do.
There was a lot of detail in there,
but we just need to run the tests.
There were four tests altogether
and hey presto, they all worked.
All right, so have a look to that again.
I think the main point is
we're mocking the useState function
and we're mocking the callback function to be invoked
when my component tries to change the value
and we can then check
that the updated function was invoked
when it should have been with the correct values.