- [Instructor] All the tests we've seen so far
are what we call resulting state tests.
So in your test function, you call a method,
the method that you're trying to test,
and then you have an expect statement,
an assertion to check the return value, for example.
So typically, you just have one assert statement
or one expect function per test function
because if you have multiple assertions,
if an assertion fails, it throws an exception.
It doesn't go on to the next expect statement.
So, you know, there isn't any point
in having multiple expects per test.
Generally speaking, you should have one expect statement,
one assertion per test function.
Now, there are some exceptions to that rule.
Imagine you call a function and it returns an object
and that object is complicated.
And in order to make sure that the object is correct,
you've gotta have multiple expect statements
to check every aspect of that return object.
Now that actually makes sense
because you're just testing one logical scenario.
You might need to have multiple assertions,
but it's still one logical outcome that you're testing.
So let me give you an example of that.
Have a look at this code here.
I've got an example that shows a resulting state test
and there's a hypothetical code example.
I've got some hypothetical function
that returns a date, d.
And that I want to check that the day,
and the month, and the year are all valid.
So, you know, I've decided to implement that
as three separate expect statements.
And that actually makes sense
because it's still one logical outcome
that I'm testing.
By the way, that's my birthday, the 3rd of December, 1964.
Now, if you're dealing with dates in lots of your tests,
you might find that lots of your tests
end up having a day, a month, and a year check.
So you might find this kind of code
repeating in lots of places in your test functions.
So having repeating tests is just as bad
as having repeated code.
So what you might consider
is to encapsulate that complexity into a helper function.
So sometimes it's handy to define a custom assertion method,
which encapsulates a complicated check,
like a day, a month, and a year.
You can put all of those checks into this helper method
and then just call it.
You can reuse it in multiple tests.
I'm gonna do that now.
I'm gonna take that code in InVision Studio,
I'm going to refactor, I'm going to extract that common code
into our global scope.
Yeah, that'll probably be good.
I'll call that function
test day month year,
like that.
Okay, so it takes in a date object
and then it kinda validates the day,
the month, and the year.
The refactoring hasn't been quite as useful
as might hoped it would be
because it's kind of hard-coded,
the value for the day, the month, and the year.
So I think I'm gonna parameterize it,
I can do, instead of having a hard-coded parameter here,
I'm going to parameterize it with
expected date,
expected month,
expected year.
So it parameterizes it.
And then, obviously, here I'd be using the expected day.
Check that the incoming date object
has the expected day.
Check that the incoming date object
has the expected month.
And also that it has the expected year.
(keys clicking)
Like so.
Okay?
And then up here, when I call it in this occasion,
I can just say, well that's the date object
that I'm testing.
I want to check that it's got the expected day,
the expected year, ah, month,
and the expected year, like that.
And what I've done now
is I've got this function here
encapsulates some complex test.
Obviously, that could be as complicated as I want it to be,
but it's really useful.
So I could reuse that helper function
in any of my tests that needed it.
So that's actually,
avoiding duplication in tests
is important, otherwise the tests
are gonna start getting hard to maintain.
Okay?
So that's a useful technique.
Right, another kind of test that you can have
is a guard test, a precondition.
Basically, what you do is you have an assertion
at the beginning, like an expect statement,
at the beginning of your test method.
Then, in your precondition,
you can check, is the object in some known initial state?
Before I do anything, let's just make sure
that the incoming object
is in some initial known state.
Then, I invoke the method and the test as normal
to, you know, to do something to the object.
And then afterwards, I can have a resulting state test
to see what's the state of my object afterwards.
And that's effectively a postcondition, okay?
So it tests the final state of the object.
So what you've basically done
is to ensure the object was in some initial state,
I called a method,
and now it's in a different, changed state.
That you have actually,
the object has actually gone through a transition.
Let me show you an example of that.
I've got an example here of a guard test.
Imagine you've got some hypothetical person object
and you want to add to this person's fleet of cars
in their drive.
You want to give this person a Bugatti Divo
and then you want to say afterwards,
let's get all the cars,
imagine that's the collection of cars for this person,
make sure that that collection contains a Bugatti Divo,
okay?
What, is this a valid test?
Not necessarily.
It could be the case that the person
already had a Bugatti Divo,
so adding the Bugatti Divo
hasn't necessarily changed the state.
Maybe that method failed,
but maybe we got away with it
because the person had a Bugatti Divo at the beginning.
So the only way that it can check
that that method's actually worked
is to say, well, let's validate
or let's ensure that this person
didn't have a Bugatti Divo initially.
Let's do that.
Let's have an expectation.
This is a guard condition.
Let's say that we expect
when we get, when you say to the person,
dot get cars,
when we get to the cars for the person initially,
I want to make sure that initially,
it does not contain
a Bugatti Divo.
Okay, so this would be me.
I think it would be most people on the planet.
Let's first of all ensure that the person,
if I get the person's cars,
that person doesn't have a Bugatti Divo initially.
Okay, good, we know that.
Otherwise, you know,
if the person did have a Bugatti Divo,
it would fail the test.
So assuming that the person doesn't have one initially,
when we add one and then we verify
that the person now has one,
it validates that we've transitioned
from the no Bugatti to yes, I have a Bugatti.
That means this method has actually worked.
Okay?
So we were in that state, now we're in that state.
Something must've happened in this method to change state.
It's a, we've undergone a transition.
A Bugatti-free zone to a Bugatti ownership zone.
Okay, that's still on my Christmas card list,
by the way, Bugatti Divo.
The third type of test we could look at
is a delta test.
This is often used for performance measurements.
You don't necessarily know
what the initial state of the object is
or what the final state is,
but you know what the delta is going to be,
a relative change.
So you don't know the absolute initial state
or the absolute final state,
but you know what the delta should be.
So like I said, performance testing is a good example.
When you do integration tests,
when you're integrating the user interface,
your code, the backend REST services, a database.
Maybe you're doing performance checks
to make sure that it's fast enough.
So what you could do is,
you don't know the absolute time
or you don't really care what the absolute time
is at any given juncture,
but you want to know the difference in time
that the operation took, how long did it take.
So record the record the time at the beginning
of the operation, do the operation,
and then record the time afterwards.
And then, you can calculate the delta
to see if you have sufficiently adequate performance.
So I've got some pseudocode to demonstrate that, as well.
This is my demo of a delta test.
I've got some hypothetical function.
You could use the JavaScript date API.
Get the current time in milliseconds.
Who knows what the current time is?
Whatever it is, do something, okay?
So imagine that's some kind of REST service call.
And then, get the time afterwards
and let's measure the time difference.
Imagine that's the time difference in milliseconds.
Our performance test is that the time delta
should be less than 5,000.
So that's an example of a delta test.
I don't really care what the final time is.
I don't really care what the initial time was.
All I wanna look at is the delta between them,
what's the difference between the two.
Okay, so these are the common types of tests
that you have in JavaScript or other languages:
resulting state tests,
where you have assertions at the end of a method.
Maybe we factored into a help function, like so.
A guard condition to check the incoming state of an object
as a precondition,
so that you know it's gone
through some kind of transition by the end.
And then, a delta test,
where you measure the difference
between the end condition and the start condition
to make sure that, for example, performance is suitable.