Hey everybody has gone this is Caleb with Dev slopes dotcom and in this video we're going to be talking
a lot about unit testing but specifically related to asynchronous operations.
If you're using a sync code there is a very specific way that you have to use unit testing if you're
going to be using it and you should.
So let's go ahead and dive right into it.
So I'm going to pull open a new project.
This is different than the previous project at the beginning of this of this section.
And basically I'm going to show you what it is I'm going to walk you through the code and then we're
going to implement some tests and test the asynchronous operations.
So this is a little app called Photo load and I'm going to go ahead and pull open the simulator here
so you can see what it is.
Basically you're presented with a table view with some placeholder images images download from online
and then what happens is as soon as they are all 100 percent downloaded they are stored into a cache
so that they do not you know initialize.
And it's nice and smooth and very very clean.
So we're going to go ahead and walk through the code.
It's a very simple app downloads images from the internet stores them into a table view in their own
cell.
When the image is done downloading which is very cool.
So let's go ahead and take a quick look at the View Controller.
We've got a basic table view linked up.
We've got its data source and delegate set and then we return the number of items for the number of
images I hardcoded that in which you wouldn't normally do.
But for this example it's ok.
Then basically I created an instance of a cell that's of type photocell.
We'll get into that in a second.
And then we return it and the height for each row is 200 for the photocell.
Basically there is an image view and there's an activity indicator view when the cell first loads the
indicator is spinning.
It's not hidden.
And then there's a function that can be called upon each cell which you can see back here in the table
view for each cell we call download image.
And basically we're passing in an image you are l from an array that I'll show you in just a second.
But basically what happens is we're utilizing what's called and ask cache k.
And basically we are saying hey if there is an image already saved into the cache we're going to set
the image view to be that image.
But from the beginning they won't be right.
They have to be downloaded first.
So if that's not the case it's going to go ahead and continue to the u r l session.
So what we're doing is we're passing in the U R L we get a completion handler with data a response and
an error which all three are optional.
If the error is not nil we're going to go ahead and print the error and then return.
Now granted we could you know call fatal error and it would do the same thing it would crash it would
print what's wrong but I'm assuming that there's not an error.
We're going to go ahead and put this on the main queue using dispatch queue and our sorry the main thread
and we're basically going to take the data that comes from the session response or sorry from the URL
from the data task that's what I'm trying to say.
We're going to take the data from the data task and use that to create a UI image then we're going to
set the image view to that.
We're going to stop the animation of the spinner which also hides it and then we're going to set that
image into the image cache.
So it's save there so that if I scroll down on my table view and the image is Decoud the cool thing
is as it comes back up it's just going to be able to instantaneously pull it from the cache.
You're not going to see any weird switching of images here or any kind of glitchy behavior it's just
buttery smooth because everything has already downloaded and saved locally to the image cache on the
device.
Then of course at the end we resume to finish the The download and then our images is saved.
OK the helper's file basically just has a giant array of all of the you or else the images I'm using
pixels which is a very cool Web site for non attribution royalty free images that you can use in any
of your projects.
So those are the you are Elle's those are downloaded by each cell and then displayed in the table view.
So that's the code.
Now we need to add a target for testing.
So go ahead and go up to the top file new target click on that and then scroll down.
Choose iOS.
Unit testing bundle.
And the name photo load test.
That's fine because that is basically what it is we're testing our project.
Click Finish and it'll add in a brand new target right here nice and shiny.
And go ahead and you know open that folder up and then right click we're going to go ahead and basically
create a test file for our view controller because we're going to test the asynchronous operations that
happen when we download a photo.
So go ahead and click new file unit.
Case class.
And then what we're going to do is we're going to name this photo view controller tests.
And of course it's going to be a subclass of X-C test case.
Click next and save it and of course we're presented with the usual screen get rid of the example functions
leave you know set up and tear down.
And we need to call at testable import and our project is called Photo load.
So we're going to import that now since we're testing this new controller we're going to go ahead and
create a variable that will hold the value of our test view controller.
So to do that.
Type.
Var sut meaning system under test.
And that's going to be of type photo the controller put an exclamation mark because we are definitely
going to have a value and we're going to set it up inside of our setup function that's the whole point.
So let's go ahead and create an instance of our storyboard by typing let storyboard equals UI storyboard
the name of course is main.
Whoops that's not right.
The name is main and the bundle is nil.
Then we're going to say Sutt system under test.
We're going to instantiate it by typing storyboard dot instantiate view controller with identifier and
the identifier that I've already set for this project is photo view controller.
And then at the very end to make sure that it comes in as that type we're going to forecast it to be
of type photo view controller.
Then in order to actually get this to work properly as a controller test we're going to call sut dot
load view if needed.
All righty.
Very very cool.
So for the tear down code we're going to go ahead and at the top we're going to type sut equals nil
and we're basically going to just delete that instance of our system under test for every single test.
Now like I said we're going to test an asynchronous operation and that asynchronous operation is our
data task right.
Actually downloading from the Internet and we're going to test to make sure that that works the way
that it's supposed to.
So go ahead and write this test phunk test photo download async operation.
OK.
And let's make some more space here so this is more in the center of the screen.
There we go.
All right.
So instead of comparing the image to the image that's already on the device what we're going to actually
do is we're going to go ahead and use the image that I've saved.
Check it out in the assets folder you'll notice there is the for the photo here pixels photo 7 6 8 2
0 8.
I'm going to copy that and go back in here.
That's going to be our expected image that we are going to attempt to download.
But we're going to go ahead and compare a data type that's easier to compare.
And it's already built into the image so go ahead and let's just check.
Well you know what we should actually write what we're testing.
Test photo download image orientation is identical.
We're going to make sure that the orientation that we expect is actually what results.
So go ahead and type let expected.
Image orientation.
And we're going to go ahead and just call you I image and you image named right here and I'm going to
use the name I just copied pixels photo 7 6 8 2 1 8.
And what we're going to do is we're going to go ahead and access the image orientation which is of type
II image orientation.
Now what we're going to do is we are basically going to use you Aurel session to download our image
the same way that we do.
But I'm going to show you a new thing that you need to know for testing that is regarding asynchronous
code because as you know async code does not always perform exactly the same way every single time.
Sometimes a particular piece or instance or iteration might take a little bit longer than another and
sometimes it might be faster.
So what we're going to do is we're going to use what's called an expectation and I'll talk about that
in just a second but first let's get you are Arel ready by typing.
Guard let you RL And that's going to be equal to you RL And we're going to create a from a string.
Now the string we're going to get that from our helper's file from the image you are all strings array.
And the third item here I guess the fourth item but the item at index 3 is the 7 6 8 2 1 8 image that
we want.
So we're going to go ahead and call.
Image your L strings and pull out the item at the third index just like that.
The reason I'm using guard let here is because you are l comes in is optional and I'm going to make
this a non-optional instance if this works out properly which it will because that's how that's how
it works I promise.
Otherwise we're going to go ahead and say ex-city fail and then Colan return.
So basically our test will fail and then we'll turn if we can't do this.
And now what we're going to do is we're going to create what is called an expectation.
So Type let session answered.
OK.
That's the name of our expectation and let's actually call it that expectation equals and we can call
it a special function called expectation which is of type or I guess returns X-C test expectation.
So give it a name and the name is session right because we're going to be using your L session and so
we're going to make sure that the session answers and returns a result to us now an expectation.
The cool thing is we can basically call this expectation here session answered expectation.
And we can call a little function on it called fullfil and it marks the expectation as having been met.
So as soon as our image is downloaded we can call fullfil and then we can basically say hey our assertion
right we're going to be asserting that you know our image orientation is equal to our expected image
orientation.
We can basically say hey let's wait for a set amount of time maybe five seconds maybe 10 seconds to
make sure that the task finishes even if it's a particularly slow or particularly speedy run through
our asynchronous code.
So go ahead.
And now we're going to actually download our image using your SSN.
So type that URL session shared data task and we're going to choose the option with a u r l and a completion
handler pass in the U R L and then press enter on the completion handler name these like you should.
Data response and error.
And then what we're going to do is we're going to make sure that first we don't have an error.
Let's make sure that we actually did this right.
So if error is not equal to nil we're going to go ahead and say x c t fail.
And the cool thing is we can actually pass in an image here.
Well you know what let's go ahead and let's just say if less error equals error.
And you know what if.
If air comes in is nil.
It's not even going to work so well you know what.
How about this if let air air and air is not equal to know we can pass in our message.
Error dot localized description.
You know what it's given me a problem here it's saying that we're not an optional type cannot be used
as a boolean not trying to use it as a boolean you know.
OK how about why don't we just go with this if let error equals error.
We can go ahead and then use LCT fail erudite localized scription.
OK.
So if we get an error our test will fail and we will get a read out of whatever the error was.
But now we need to go ahead and use the data.
So if let data equals data we're going to create an image now from that data.
So guard let image equals you judge from data.
OK.
And go ahead and pass that data there else.
Ok if that doesn't work we're going to say ex-city fail and then return OK because this is a card we
can return out of it's that we don't get a crash.
I mean of course our test will fail but that's it.
So we now have an image that's great.
Now what we want to do is we basically want to say hey session answer the expectation we can fulfill
it because it's now done.
And what we can do now is finish up our data task by calling dot resume and that's you know you have
to do that for you session all the time.
And now what I can do is I can actually use my expertise assert equal and I can check to see if the
orientation of this image is the same as my expected orientation.
So go ahead and type image dot orientation dot image orientation and then compare that to expected image
orientation and you might be thinking how is this at all different to what we've been doing in the past.
This is where it comes into play.
Wait for expectations.
This powerful function is going to allow us to set a time interval.
I'm going to say eight seconds.
You can choose whatever.
But basically it's going to wait for eight seconds until fulfilling.
And so we're basically setting a delay ensuring that we have enough time to actually download the image
in case it takes a long time maybe your Internet is just really slow for just a few minutes.
Your test would fail.
But this allows you the time to actually make sure that the test passes when it should.
So the completion handler can be nil and we can now run this test so go ahead and press command you
and see how we do.
Art We're going to run our test and see how we do.
Basically we're just making sure that when the photo downloads the orientation comes in the right way.
Running our tests is cool.
Very very cool.
And look at that our test passes.
That's awesome.
You know what to make sure that this actually works the way that it's supposed to do what I can do is
I could change something you know that might very very minimally modify the code.
Of course this image name is you know 7 6 8 2 1 8.
Let's change it to 7.
That file doesn't exist.
So if I run this we'll make sure that we're actually getting the results that we want.
We're just going to make sure our tests work.
It's testing it's running and our test should fail of course because that image is not even there so
it won't be able to compare it and look at that.
The image is not equal to the image it says it's not even there.
So very very cool.
If we run it again we'll see that our test really does work and it really is checking to see if the
orientations are equal which is really neat.
OK.
Three two one done hopefully maybe an hour and it passes.
Look at that super super awesome.
So guys this is just one example of how you can test asynchronous operations by using what are called
expectations.
You can set an expectation say when it is fulfilled and then give it a time out time so that you can
actually wait for it to process.
This is really really helpful when testing network code especially live network code.
So let's go ahead and let's head over to the next video where we're going to use what is called a mock
K it's like our own version of an already created class and we're going to be able to use that mock
to basically test our server to make sure that everything is running and making sure that if someone
were to accidentally change the text of a certain endpoint the test would fail making sure that we don't
have any weird server crashes or tricky bugs to fix or we're going to look into creating a mock u r
l session and making sure that our our host and our paths and our end points are all working the way
that we're supposed to in this particular application.
So I'll see you there.