- In this section, we're going to investigate
shallow rendering via the shallow function.
So if you remember how it works,
you call the shallow function and you give it a component.
The shallow function just renders that component,
but not its children, so if your component
had subcomponents, those subcomponents will not be expanded.
Just the top-level component gets rendered.
And that's very useful for unit testing.
You can test this component without worrying
what its subcomponents might render.
So we're gonna have a look at the example.
The actual example itself is the same as before.
We're just going to look at some additional tests
that we haven't yet seen.
So in the demo folder, if you go through Lesson 10,
Enzyme_Shallow, in that folder,
using the Create React App tool,
I created a demo application, demo-app.
Okay, so here it is.
Just a very quick reminder of what this application contains
before we dive into the tests.
So there's a Product class, there's a Product class there
that basically describes the details for one product.
I have a top-level App component
which basically renders a product list.
So I've created a sample array of products.
I've passed that array into my ProductList component.
Display those products, please.
And here's my ProductList component.
It receives an array of product items.
There were three of them.
If the array was empty,
then it would return this content, "No products in stock."
Otherwise, it would say, "There are 3 items in stock,"
and for each product, it would render it
using the Product component.
The Product component decides how to render one product,
and it renders it with a certain style,
the description, the price, and the units in stock.
Okay, and then if I go back up to my ProductList,
at the end, it displays the total value.
It adds the values together and it displays that
that using the TotalValue component,
get the total cost, and display it
in a stylized div, like that.
Okay, so we've seen that code before,
and this is what it looks like,
each product with a heading,
and a suitable total value, like so.
So the code isn't new. The tests are going to be new.
Right, so, let's take a closer look.
When you call the shallow function,
it returns something called a ShallowWrapper,
an object that encapsulates the HTML,
the content, that has just been rendered.
And this ShallowWrapper object provides an API.
It has functions which allow you to drill into that content
to see what's just been rendered.
So, using this, when you call that function
and it returns one of these,
you can call functions on this then, such as find.
In the rendered content, you can find content.
You can use a selector here as CSS.
So it could be the name of an element, like div,
or it could be a CSS class, like important,
or it could be an ID.
Right, okay. You can get back a list of all your children.
You can get back the first child, or the last child,
or the child at a particular index, like,
give me back my child at index 3.
You can also get properties.
This is important.
Your component will be attempting
to render other components.
What properties are being passed in to those components?
What property was passed in?
What was the value of the x property, okay?
Am I invoking my child content, my child components,
with the right properties?
They won't be rendered, but I can verify
what properties I'm passing into them
to make sure that I'm calling them correctly.
As I said, if one function calls another function,
am I passing the right parameters into that function?
It's not going to invoke the function,
but am I passing the right properties in, just in case?
So there's quite a wide API, and it's quite handy.
It's quite simple, actually.
The documentation is pretty good.
Let's take a quick look at the link there.
So here it is.
If you go to that link, it gives you a little bit
of an overall description of shallow rendering.
We kind of discuss the high-level principles,
a few examples as well,
and then, if you drill down, the ShallowWrapper object.
Remember, when you call the shallow function,
it gives you back one of these objects,
and this object has various different functions
which allow you to locate content
in your rendered component, okay?
So it's quite a rich API.
It's quite sophisticated.
Not massively complicated, but good enough
for you to locate any kind of content
in your rendered web page.
All right, so let's take a look at the test.
We're gonna put all our tests into ProductList.test.tsx
So I've written three different tests in there.
Let me give you a quick look before we dive in.
So here's my code.
We're gonna look at ProductList.test.tsx
And I'll just collapse it down.
There we go.
So you can see, first of all, I've imported
the shallow function from Enzyme,
plus all of my classes and components.
And then, oh, I've set up some sample data
that I'm gonna use in some of my tests.
The first test just checks, you know,
when you're doing unit testing,
you should check the simplest scenario first.
The simplest scenario, what does my component render
if the ProductList is empty?
We'll have a look at that test first.
Then we'll have a look at, if the ProductList is not empty,
if it contains actual populated content,
what does it render in that case?
And what properties am I passing into my child components?
We'll have a look at that in the third test.
Okay, so let's have a look at the first test first.
I'll just expand it here.
And then we'll spend a few minutes
just dissecting the code in here
and describing what's going on.
Right, so here it is.
So first of all, I call the shallow function.
I'm gonna render the ProductList component,
because that's what I'm trying to test.
So render the ProductList component, oh,
and pass in an empty product array.
Don't forget the curly brackets.
You know, when you're using JSX, or TSX,
when you're in the middle of an XML block,
you've gotta use curly brackets to slip
back into JavaScript mode,
because the square brackets here is JavaScript.
Empty array.
It passes an empty array into ProductList.
Before we look at the test, let's remind ourselves
what we expect to get back in that situation,
because I've already actually written the code.
So in my ProductList component,
if the products that get passed in is an empty array,
then it should come in here, shouldn't it?
The length is 0.
This is what should be rendered,
a div with an h1, "No products in stock."
That's what we would expect to get back.
When we render ProductList with an empty array,
this is what we should get back.
Let's test that we do get that back.
Right, so having called the shallow function,
it'll give me back a wrapper to that rendered content.
I'm expecting that there should be,
in that rendered content, there should be one h1 element.
The find function finds all matches,
but there should just be one h1,
so the length of this collection of h1s should be 1.
I should have got back one h1.
In other words, I should have got back this h1 here.
Okay, let's check the text in there.
Let's find that first h1.
Let's get the first h1.
Okay, there is only one.
Let's get the text out of it.
The text should be "No products in stock," okay?
That's what it should be.
Well, obviously you can see it is.
To check that that's what we got back.
There we go. Get back all h1s, just the first one.
Give me the text content.
That text content should be "No products in stock."
Also, because there were no products in stock,
it shouldn't render any Product elements,
and it shouldn't render a TotalValue.
In other words, we didn't come into this part
where it rendered Products and where
it rendered a TotalValue.
We shouldn't see that in this case.
We should just see that there should not be
any Product elements rendered,
and there should not be a TotalValue rendered, either.
That shouldn't happen when you don't have any Products.
We should just get back that.
So let's verify that.
Let's verify, if I tried to find a Product component.
Notice that you can search for components as well.
They wouldn't be rendered,
but they would be present in your document structure.
You'll either use quotes, if it's a regular element,
like h1, or you use the component name,
if it's an actual component.
Check to see that there are no
Product components rendered.
Check to see that there's no TotalValue rendered, okay?
It doesn't make sense to have these
if the product collection is empty.
Okay. So that's good.
Let's have look at another one.
In this one, now, I'm actually gonna pass in
some actual products.
So, a quick reminder.
In my product test, I set up an array
of three products here, and what I'm gonna do now
is to see what gets rendered
if I pass those into my ProductList.
So that's gonna be the second test.
The second test is when I actually pass products
into my ProductList.
The products we pass in are the products up here.
There were three of them.
Let's see what gets rendered in that case, okay?
So first of all, render ProductList
with an array of products, three of them,
and let's see what we get back.
So let's just, again, remind ourselves
what we expect to get back in my ProductList.
If you pass in an actual array,
then it should come down into the else part, shouldn't it?
And what we should see being rendered
is an h1, "3 product(s) in stock."
Let's check that happened.
There should also be three Product elements rendered.
Well, three Product elements created.
Remember, when you're doing shallow rendering
there will be a Product element in the content,
but it doesn't actually expand it, okay?
So we will get back an h1.
We will get back three products.
They won't contain any content,
but they'll still be there in the structure.
And we'll also get back a TotalValue element.
And again, it won't actually expand it.
Shallow rendering doesn't render it,
it doesn't render child elements, okay?
They are there, but they're not expanded.
So we should get back an h1,
three products, and one TotalValue.
That's what we should expect to get back
when we pass in an array of products.
They should come into there and do that,
and that's what my tests establish.
Was there an h1 element?
There should have been one h1 element.
The text inside that element
should be "3 product(s) in stock."
What I've just verified there is this.
Did my rendered content do that bit correctly?
So that's what my test is establishing.
Did my component render this correctly?
That's what I expect.
Let's verify that's what we got back.
Right, what about the next part?
In my wrapper, the rendered content,
or the content that was created,
there should be a Product component,
there should be three of them,
because I passed in an array of three Products.
Then it should have basically looped
through this three times,
and it should have created three Products.
Again, it doesn't expand them,
but they are there in the structure.
Just the component itself, but not its content.
There should be three Product components.
Okay, right.
Those Product components are not rendered, okay,
so it doesn't actually go into the Product component.
So I'm having a look at this and verifying,
I'm just proving, if the Product was rendered,
if the Product was rendered,
the Product would have rendered itself like this, okay?
If the Product was rendered,
it would render a div with a CSS class of productItem.
It doesn't do that rendering
when you call the shallow function.
Even though it knows there are three Products,
it doesn't actually call the Product component.
It just exists, but it doesn't go into it.
So in other words, we should not find
any of this expansion in our output.
So let's verify that we don't have any productItem divs.
Let's verify that there are no divs with productItem, okay?
This proves that although the Product component existed,
it didn't get expanded, it didn't get rendered,
so there was no div with a CSS class of productItem.
That length is 0.
In other words, the Product component
was not actually rendered.
A similar thing for TotalValue.
The TotalValue component exists, but it wasn't rendered.
So a reminder here, in my ProductList,
a shallow rendering, it'll realize
there's going to be a TotalValue component.
That TotalValue component exists,
the TotalValue component does exist, but it's not expanded.
So the content of my TotalValue component is not expanded.
It does not generate a div with a totalValue class.
There is no div with a totalValue class.
So the components exist, but their
child content is not rendered.
That's what shallow rendering means.
Don't expand the child components.
Okay, so hopefully, that makes sense.
Right, the third test is kinda similar.
I do a shallow rendering of ProductList.
So we're passing in some products, okay.
So here's my ProductList that's going to be invoked.
I'm invoking my ProductList component,
passing in three Products.
It should come down here.
What I'm testing now is, what properties
did it pass in to the TotalValue component?
I'm not going to expand this component,
but I can verify, did my code pass in the right parameters?
Did it pass in the correct parameters into the component?
Just like saying, I'm testing this function.
I know this function is gonna call that function.
That function isn't actually going to be invoked
with shallow rendering, but can I verify
that I've passed in the correct parameters?
So that, obviously, if it was invoked,
it would render the right stuff.
So that's what I'm doing here.
Shallow rendering, get the return content via the wrapper,
find the TotalValue component.
So there's the TotalValue component.
In fact, remember, when you call the find function,
it gives you back a collection
of all TotalValue components, if there were many.
Well, there's only one.
Give me back the first TotalValue,
find the property called products.
So it'll basically find the products property,
and it'll say, what have you passed in to that property?
You should have passed in an array whose length is 3.
Get the products property.
What value did I pass in?
I passed in an array, actually.
Check that the length of that array is 3.
In other words, when we render this component,
does it attempt to render that component?
Does it pass in a products property,
an array whose length is 3?
Have I invoked that property,
have I invoked that component with the correct properties?
So it's a bit like mocking, really.
There is this other component
that I'm thinking about calling.
Let's just check that I'm passing
the right parameters into it so that it
will work later on, hopefully.
Okay, so that was quite a lot going on there.
You probably wanna have another look through those tests
and just make sure that you're confident.
But all we need to do now is just run the tests
and cross our fingers and hope it works,
and it does work.
So we had an example, a component
with an empty bunch of products,
returned a simple "No products in stock,"
we passed in a collection of three Products,
and ensured that it rendered them correctly,
and then we passed in the same, but this time,
we just verified the properties
we were passing into a child component.
Okay, so that's basically shallow rendering in a nutshell.