In this video, learn how to use the LayoutTest library in order to specify the data with which you want to test your view. Providing different data to your view allows for testing a variety of cases that you may not have even thought of yet. The LayoutTest library makes it simple to have your test run with a variety of data input with a simple, clean syntax in both Swift and Objective-C.
- [Instructor] The first step in writing a layout test is to specify the data your view needs to layout all of its elements. So let's take a look at the exercise files here. The one you're going to be looking at is in the 02_01 directory, and it's called UsingTestData. I've already included the Pod file, but you're going to need to run Pod Install to make sure the dependencies are downloaded. For now, I've already done that, so I'm going to open the workspace. Let's take a look at the view we're going to be testing.
As you see here, we have UsingTestData, the app, and we have an item called EntityRowView. Let's take a look at the zip file. As you see, we have an image on the left that is going to be either an image of a person placeholder or a company placeholder. Then we have a name label and a subtitle label. On the right we have a share button. As you can see this is a pretty common looking view that you could be used in a UI table view cell or a collection view cell.
What we'll need is an asset name for the image, text for the name label, text for the subtitle label, and nothing for the share button because that doesn't change states. Let's take a look at how we specify this data in layout test. Let's open up the view code itself, EntityRowView.swift. Here's the class for the row view itself. As you see we have an entityType for the person and the company that gives us a different image and we have the different properties that we're going to be setting for the image view, the name label, subtitle label, and the button.
We also have a make method, which will instantiate this view from the nib. In order to provide the test data for layout tests, we're going to make a new file in the UsingTestData test directory, so let's select that first. Hit File, New, File, or cmd + n, and then we're going to make a new swift file. This is going to be an extension on the EntityRowView itself that only lives in the test target, so we're going to call this EntityRowView+LayoutTest, so it's going to add layout test functionality.
This naming convention kind of comes over from Objective-C, but it's also useful in Swift. Let's hit Create, and we see, it belongs to the test target only. That's what we want. So in EntityRowView+LayoutTest, we're going to make a new extension on EntityRowView. We're going to need to implement a protocol called ViewProvider, which is in the layout test framework. So let's import LayoutTest. If you're autocomplete doesn't show LayoutTest, I would hit cmd + u to run all your tests.
That will make xcode pick up the product. So now let's create an extension on EntityRowView, and we're going to implement ViewProvider, and this has two static methods that we're interested in. One is dataSpec for test, and I would like to use my autocomplete, and it's not working, so I'm going to hit cmd + u again just to make sure. As you see, xcode is telling me there's an undeclared type EntityRowView.
That's because EntityRowView is declared as internal, so in this separate target, we're going to need to import it as testable. We're going to import the UsingTestData product. So now we don't have this issue any more. Now it's just saying that we don't conform to the protocol. So there are two methods that we're going to need to implement. One's called dataSpecForTest. If we just start typing data, we'll see it says dataSpecForTest.
The second one is viewforData as you see here. These are the two methods we need to implement. As you see, it's going to show you there's an error. That's just because they're declared public in the protocol, so you need to declare them public here. In the dataSpecForTest method, we're going to create a dictionary where keys are used to identify the data we need. You can pick any key name you want. So in this case, we have three different sets of data we need.
We need one for the name, one for the subtitle, and one for the entity type. As we said, the share button doesn't change, so we don't need any data for that. So what we're going to do here is we're going to return a dictionary with this information. So we're going to type return, create a new dictionary, and the first one is going to be the name, so let's just call this name, and the value we need strings here because it's a UI label. As we've talked about in a previous video, to get these strings, we can use the convenient StringValues type and just use the empty initializer.
If we want to make sure we always have a name, we can say required is true. So this way, it will never give us nil. Whereas for the subtitle, similarly, that's just a set of strings as well, but this one, maybe someone doesn't have a subtitle or a company doesn't have a subtitle. So we'll leave required as false, which is the default. Lastly we have the entity type. Now here's where it gets a little interesting. The entity type is an enum, and it can have two different values.
So let's say entityType, so for our values we can create a DataValues and give our own values. And for here, we have two values we can have. We can either have company or person. So we can create EntityRowView has an internal type called EntityType, and we want company as one, and then same thing for the person.
And actually what you may notice is, we're actually making an extension on EntityRowView, so we don't have to use this type in front of it, so we can delete that, just have EntityType. Let's move this onto a new line, so it's a little easier to read. And here we go. Now we have our three properties, name, subtitle, and entity type. As stated in an earlier video, the string values will give us all different types of edge case data for the strings.
Now we've created a code that will specify all of the different data combinations that will be used in our tests. It's that easy to run all the different combinations. Now let's implement the viewforData method. This method is called on each run of the test. Let's talk about the parameters in this function. The first one we have is data, and this data encapsulates one combination of the data we specified in the previous method, so for instance, we will get a dictionary with the name, subtitle, and entity type keys, and we'll get one value for each of those.
So name may have a value of some small string, subtitle can have a different string, and entity type is going to either have .company or .person. The reuseView argument is there to emulate usage of the DQ reusable cell method and UI table view and UI collection view. If you plan for your view to be used in a table view or collection view cell, it's useful to use this property, so that it gets reused like it would in those situations. Reusing a view can expose other problems with incorrect layout state, so it's important to consider this.
The size parameter, we're going to ignore for now because you can choose to do something different based on the size passed in, but we don't have any need to do that. But this is not modifiable, so this is only information that can inform what you want to do with the view but not sizing information. We will go over later how to do different things based on the size. The context we're also going to ignore in this case, but any extra data can be passed in. You will see this used in a later video. Let's create the view for testing form the data.
We will reuse the view passed in if it's not new, otherwise, we will create a new one. Let's do this now. We have let view = and we're going to make a closure here that automatically executes. Now I'm going to try to unwrap a view from the reuse view. I'll just call this reuse view, and I want to see if it is an entity row view.
If it is, we're good go and we can just return the row view, and that will be put in our view property. If not, we're going to create a new one, and we'll do this by returning EntityRowView.make. So what we have here is an autoexecuting closure that will run this code to return an EntityRowView. It will either return the one that was passed in as a reuse view, or it will make a new one.
Now we have this stored in our property called view. Now we can set the properties of the view using the data that was passed in. So we will take this view, and we're going to set different information on it. As you see here we have some errors. We can fix this by just stating exactly what type this is because it's having a tough time inferring it. Also, I mistyped here. reuseView. There we go, okay. Now we should be good to go. So we can sit on EntityRowView the name labels, text to the text from the data.
So we have our data parameter. We can get the main key value there, and we're going to optionally cast it as a string. Since it's going to be an optional value, and text takes in an optional, this all works fine. Now lets do the same with the subtitle label. Data subtitle be sure to type this carefully. You can also set some static constants to make this easier so you don't have to type it twice if you'd like.
Lastly, we're going to be setting the EntityType and for this one, it's going to be slightly different, entityType, and since this is a test, I wouldn't normally say to use an as bang, but in this case it's fine because we're in test code, and if it crashes, it'll crash when we're testing it, and you can fix it. Basically just means developer error in this case. So you have an as bang and do it as an entity type.
Bang is another word for exclamation point. So when I say as bang, that means as! In Swift, this is called the forced unwrap operator. So now we've set all the properties we need on our view. Now we see that this function is view for data and it returns a UI view, so let's just return the view. You see, all our errors went away, and we're all done. You've now completed all of the code needed to provide the view to be tested. In the next video we will write and run the actual test.
- Installing the library
- Specifying test data
- Reviewing property-based testing
- Using test data and writing the LayoutTest test
- Testing views at different sizes
- Debugging with snapshots
- Dealing with common errors
- Advanced debugging tips
- Exploring catalog view