Intro to Client-Side Testing
Lavin, Mark
Intro/Background Example Project Getting Started with Selenium Navigating pages Finding elements Waiting on actions Unittesting with QUnit Why QUnit? Tests and assertions Test fixtures Additional Resources Q&A
 What does repeat all of the things that you just said? I work for Cactus Group. I'm the co-author of Lightweight Django. You can find me on Twitter. I'm Dr. Oyes. I'm MLAVN on GitHub and the rest of the Internet. If you can help me acquire the MLAVN Twitter account, like come see me after. Be interested. So what's the goal of this talk? The goal of this talk is to get you started writing tests from a practical standpoint. This isn't supposed to be an in-depth review of all available JavaScript testing tools. And I'm not here to convince you to do testing. Like, that argument, I don't think needs to be made anymore, especially not in this community. So the workflow is I'm going to show you a little example project. I'm going to show you some integration tests for that project, and then we'll go through some unit tests for that project. So what are we going to test today? This is a project that lives on file API MLAVN.org, and the source code is available on my GitHub account. It's a minimal REST API using JSON web tokens for auth. It does drag and drop file uploads. There's a limit. Please don't try to upload, like, 100 gigs of my server, please. The front end uses backbone. I'm not going to show you any of the code for the views, any of the backbone code. I'm only going to show you the tests. If you want to take the time and review the project code itself, do so in your own time. It looks like this. You log in, there's a guest log in. When you, after you log in, you see this blob and an empty line because they didn't spend any time styling this. When you drag a file, it's there, and then there's a little X, and you can delete it. That's what the project looks like. On the back end, it looks like this. There's not that many views. There's, you know, the API route which renders the single-page app. There's an endpoint to exchange an API token, the username and password for an API token. You can get uploads to list all uploads. You can post a new upload. You can get uploads, like details for a single upload. You can delete an upload. There's no put for updating. Yeah, it's super restful. But users don't care about rest. Users don't care about how restful the back end is. They want to do things on the site. They want to log into the site. They want to navigate around. They want to submit data. They want to see their data. And so those are the things that you want to test to make sure that this works as expected. And that's what Selenium is really good at. Selenium drives a browser, and you can interact with the DOM. You can fill in data. You can click links. You can assert things about the current state of the DOM. Some basic Selenium setup, this almost comes straight out of the Django docs, is to use the live static server test case and do some setup tear down of the Selenium web driver. The setup and tear down can be a little slow. That's why it's done once per class rather than per test. That means you don't get perfect test isolation, and it can lead to problems. I like Phantom.js for the driver. It's a headless webkit browser. You can install with NPM. You could replace this with Firefox or Chrome or IE, or you may want to run your test against all of them. That's all available options. Phantom.js also works well. It's installed by default on Travis CI. So it integrates well with CI environments. A Selenium test looks something like this. You tell the browser to get a page, and then you assert something about that page. So I say when I get the page, I should see the login form. So I find the login form by its HTML ID, and I look for that little DOM element that's the file upload, and the user should not be able to see that if they haven't logged in. Moving on to that, you can fill out forms. So we have a login form. We know it's there. We find it, and we get the inputs by their name. There's a username input, and there's a password input. And you use send keys to emulate, like, user typing in the form. You can also make this work with, you know, select boxes, file inputs. You can do all those things. You can fill them out. The nice thing about Selenium, and the reason you need to use the static live server test case to get really meaningful tests, is that if you try to do interactions like filling in forms, and that form element, one isn't found, it's going to error. But more so if the form element is in the DOM, but it's not visible to the user, it will error, which can give you something far more meaningful than what, like, Django's test client can give you. Not just that it's in the DOM, but that's actually visible to a user. Then you can submit forms by either submitting the form element or by, you know, we could have found the submit button and hit, clicked it. This is a little helper method. This isn't actually a test itself. A test for this might look something like this. I want to call my little helper to log in, and then I need to wait for the browser. I need to wait until something happens. In a traditional web application, this might be, like, a redirect to another page, and I need to wait for that redirect to happen. In my single-page app, I need to wait for the API call to go to the server and come back, and then the DOM is going to change. I'm asserting how I expect the DOM to change. In this case, I'm going to wait until I can see the upload. It should now be visible. I'm going to wait five seconds. It'll error if it takes more than five seconds for that to happen. Then again, I'm going to assert things. The file upload area should be visible, and now the login shouldn't be visible to the browser. That's what I'm asserting here. There are also implicit waits that are available in Selenium, and those just wait for a set amount of time. Sometimes writing these assertions on how it should explicitly wait can get a little tricky, and it's like a cheap fallback to just wait, but if you just say wait for a second, and you start doing this in a lot of tests, you've added a second to each test run, and then you've added another second to each test run, and it really starts to add up. It can really add useless time to your test running, and these tests aren't particularly fast to begin with. Follow the Xenopython, explicit waits are better than implicit waits. Users aren't always right, so other things you might want to assert with Selenium would be they fill in their password, but they miss a character, and they should see an error, and make sure that you're rendering the errors correctly. These are great end-to-end tests, but they're slow, and they can be fragile. If you're thinking about how we're building these assertions, we're finding elements by HTML ID, we're finding elements by class name. If a front-end developer or designer comes in and tweaks some of the class names, now this test fails, even though the functionality should still work, my test fails, or if the error message changed slightly. You want to use these as they couple well and pair well with other tests in your test suite. These are integration-style tests. Let's talk about pure JavaScript testing. For edge cases, really tricky logic, it's hard to beat isolated unit tests, and sometimes this goes out the window when we write JavaScript, because we're primarily Python-focused, but I feel like the Django community really values testing, and maybe the reason is people don't know what tools are out there. Part of why it's hard to know what tools are out there is that in the time I've been talking about this slide, I think eight more testing frameworks have been written for JavaScript. I'll tell you about the ones that were in existence when I started my talk. Some of the popular ones are QUnit, Mocha, Jasmine, Karma, Protractor. They sound like testing frameworks, and you probably would have Googled Karma and thought, yeah, definitely, like that Jasmine, that was the one I was thinking of. So I'm going to save you some trouble and tell you to just use QUnit. I'm going to tell you why, and if you don't agree with my why, check out one of these other ones. I like QUnit because it has a familiar XUnit style, that style that everyone has ripped off from small talk since the beginning of history. There are other frameworks that choose to do a more like BDD style, like that lettuce cucumber style that we would have in Python. Those would be like Jasmine, and Mocha is actually really weird. They're like, just choose however you want to write your tests. You kind of want things to be a little more opinionated, or I like things to be a little more opinionated. So Jasmine is a very popular BDD style testing framework. If you like XUnit, that's a high barrier for some people. If you like XUnit, use QUnit. It's used by large projects like jQuery and Trey Hunter. I don't know if he's here. Thank you. I'm going to go through this to Django 19, fantastic work to cover unit tests for the admin. Setting up QUnit is about creating a static HTML page which includes all of the QUnit pieces. There's some CSS for QUnit, and then there's the QUnit JavaScript itself. For my project, or the project I'm showing you here, there's the code that we want to test, which is the models and the views, the backbone models and views, not Django models and views. And it uses backbone, so it depends on backbone, which depends on underscore and depends on jQuery. So those are all included. And then at the very bottom, I have two new files. That's the test models and test views. I know that the end script tags are missing. It's like a weird error with reveal.js, which is what I'm using for my slides. So put your script tags in there. They're in in the repo. Our first QUnit test uses QUnit.test, which names the test and then has the test function itself. The test function is given a single argument, which is the assertion pieces. And it has all the assertion APIs you kind of expect from XUnit, like expect or assert equals or assert okay or assert not okay or not equals. In this case, and you don't have to know too much about backbone, I hope to at least read what this test is doing, I'm creating an instance of a backbone model similar to a Django model and I'm asserting that a method call URL, that that method call returns the URL that I expect. That's really it. So again, pretty standard unit test, create an instance of a class, call one method, assert one thing, just what you want from a unit testing. Things you're also probably familiar with from unit testing are things like set up and tear down. I mean, we were looking at set up and tear down for Selenium. They don't call it set up and tear down anymore. They call it before each and after each. And it uses a QUnit module. This is the thing that I hate the most about QUnit. The module groupings of tests are implicit by ordering. So any test declared after this is part of the module. And they're all part of the module until the file ends or until there's another module declaration, which is clearly not Python. So, in this case, I want to start testing my backbone views. So before each test, I'm going to create an instance of that view and I insert it into the DOM. And then after each test, I rip it out of the DOM. So for every test that's going to run, I get a fresh copy of my view that I'm going to test. Any state that you attach to this will be available using this inside of any test in the module. So any test after this module has access to this.view. Because everyone understands how JavaScript this works, right? So here's a test that tests this view. Again, I want to call a method on my view. My view has a method called add file and it takes an instance of a model. So I create an instance of the model, I call the method, and I assert that there's a new DOM element. And I could assert more things about that DOM element. I mean, this is pretty minimal. I could assert, like, the text of the DOM element or I could have looked at seeing how the DOM element relates to the model itself. But there's no user interaction here. There's no user. There's no clicks or API calls. This is just calling methods on objects and asserting what happens when you do them. As a little bonus, QUnit doesn't come with any mocking, but it does play well with a library called Scion.js, I think I'm pronouncing that right. Also super-googleable. So in this case, I want to mock a thing. This view, this other view I want to test, actually handles the drag and drop. So when the file gets dropped, it makes an API call to the server to post. The server is not running when you load this page, so I need to mock that call. So in my set up tear down, you know, not set up tear down, but my before each, after each, I create an instance of the view and then I patch the collections create method. And again, that's a little bit of backbone to know here, but collection create actually does that post to the server. And then a test which might use this is, again, I want to fake like a user did a drag and drop. So I create a fake DOM event called drop and then digging deep into how drag and drop works in JavaScript. And there's a drag and drop, there's a data transfer and data transfer contains a list of files that the user is dragging. But I'm again constructing it like a user just clicked and created this event. And then I call the drop callback, which would be called in this case, and assert that create would have been called. The API call would have been called. And I can assert, again, more things about what that call would be. There's some hover state there for the drag and drop and it gets asserted here as well, but it's not really part of the mocking. To run Q unit test, you just load that file in your browser and it looks like this. It highlights all the things as they run, they run usually pretty fast. There are command line tools to automate this. You can use your favorite node task runner like grunt or gulp or you can just use phantom on your own. Of course, we also know a cheap way to automate things in a browser with Python called Selenium. So we could just load this file with Selenium and then assert that there weren't any failures by reading the DOM. It's kind of a cheap way. I don't know that I would do this if I had a huge test suite of Q unit, but it lowers that barrier of entry. Grunt and gulp are non-trivial things to add to your stack. You have to really be committed to add them. So this is a way to sort of get started with Q unit tests and without all those tooling pieces. So for this project, when you run them, all these things run. There are Selenium tests which drive this browser interaction, there are Q unit tests which cover the client side interaction, or actually aren't any Python unit tests for all the view code, but those are pretty, those wouldn't be hard to write. Just too lazy. But this actually gets, I think, like there's like two or three lines that aren't covered in this case which are like all the 404 handling. You ask for a file that doesn't exist. The user can't do that in the user interface. The user can't easily do that. So it's not covered. Some resources. So basically the Django documentation on testing is, you know, your first go-to place. And the Python Selenium bindings also have great descriptions of all those API methods for waiting on events. There's different ways to find and detect when the DOM changes. In my case, as I was waiting for the visibility of an element, but you can wait for the title of the page to change, you can wait for things to disappear or reappear. There's lots of different ways that you can use that. The Q unit docs and the Scion.js docs also very helpful. Some photos are still from Flickr, Creative Commons. Thank you very much. My slides are available on this extraordinarily long link which I'll tweet out. I didn't use the Libyan DNS bit.ly.com to do it. But we have a book signing at 2.50 as well. We actually do have like five minutes. So I will take questions either now or then or everyone can run and get some lunch or find out who won the Microsoft contest. Thank you. Thank you.
 What does repeat all of the things that you just said?  I work for Cactus Group.  I'm the co-author of Lightweight Django.  You can find me on Twitter.  I'm Dr. Oyes.  I'm MLAVN on GitHub and the rest of the Internet.  If you can help me acquire the MLAVN Twitter account, like come see me after.  Be interested.  So what's the goal of this talk?  The goal of this talk is to get you started writing tests from a practical standpoint.  This isn't supposed to be an in-depth review of all available JavaScript testing tools.  And I'm not here to convince you to do testing.  Like, that argument, I don't think needs to be made anymore, especially not in this community.  So the workflow is I'm going to show you a little example project.  I'm going to show you some integration tests for that project, and then we'll go through  some unit tests for that project.  So what are we going to test today?  This is a project that lives on file API MLAVN.org, and the source code is available on my GitHub  account.  It's a minimal REST API using JSON web tokens for auth.  It does drag and drop file uploads.  There's a limit.  Please don't try to upload, like, 100 gigs of my server, please.  The front end uses backbone.  I'm not going to show you any of the code for the views, any of the backbone code.  I'm only going to show you the tests.  If you want to take the time and review the project code itself, do so in your own time.  It looks like this.  You log in, there's a guest log in.  When you, after you log in, you see this blob and an empty line because they didn't spend  any time styling this.  When you drag a file, it's there, and then there's a little X, and you can delete it.  That's what the project looks like.  On the back end, it looks like this.  There's not that many views.  There's, you know, the API route which renders the single-page app.  There's an endpoint to exchange an API token, the username and password for an API token.  You can get uploads to list all uploads.  You can post a new upload.  You can get uploads, like details for a single upload.  You can delete an upload.  There's no put for updating.  Yeah, it's super restful.  But users don't care about rest.  Users don't care about how restful the back end is.  They want to do things on the site.  They want to log into the site.  They want to navigate around.  They want to submit data.  They want to see their data.  And so those are the things that you want to test to make sure that this works as expected.  And that's what Selenium is really good at.  Selenium drives a browser, and you can interact with the DOM.  You can fill in data.  You can click links.  You can assert things about the current state of the DOM.  Some basic Selenium setup, this almost comes straight out of the Django docs, is to use  the live static server test case and do some setup tear down of the Selenium web driver.  The setup and tear down can be a little slow.  That's why it's done once per class rather than per test.  That means you don't get perfect test isolation, and it can lead to problems.  I like Phantom.js for the driver.  It's a headless webkit browser.  You can install with NPM.  You could replace this with Firefox or Chrome or IE, or you may want to run your test against  all of them.  That's all available options.  Phantom.js also works well.  It's installed by default on Travis CI.  So it integrates well with CI environments.  A Selenium test looks something like this.  You tell the browser to get a page, and then you assert something about that page.  So I say when I get the page, I should see the login form.  So I find the login form by its HTML ID, and I look for that little DOM element that's  the file upload, and the user should not be able to see that if they haven't logged in.  Moving on to that, you can fill out forms.  So we have a login form.  We know it's there.  We find it, and we get the inputs by their name.  There's a username input, and there's a password input.  And you use send keys to emulate, like, user typing in the form.  You can also make this work with, you know, select boxes, file inputs.  You can do all those things.  You can fill them out.  The nice thing about Selenium, and the reason you need to use the static live server test  case to get really meaningful tests, is that if you try to do interactions like filling  in forms, and that form element, one isn't found, it's going to error.  But more so if the form element is in the DOM, but it's not visible to the user, it  will error, which can give you something far more meaningful than what, like, Django's  test client can give you.  Not just that it's in the DOM, but that's actually visible to a user.  Then you can submit forms by either submitting the form element or by, you know, we could  have found the submit button and hit, clicked it.  This is a little helper method.  This isn't actually a test itself.  A test for this might look something like this.  I want to call my little helper to log in, and then I need to wait for the browser.  I need to wait until something happens.  In a traditional web application, this might be, like, a redirect to another page, and  I need to wait for that redirect to happen.  In my single-page app, I need to wait for the API call to go to the server and come  back, and then the DOM is going to change.  I'm asserting how I expect the DOM to change.  In this case, I'm going to wait until I can see the upload.  It should now be visible.  I'm going to wait five seconds.  It'll error if it takes more than five seconds for that to happen.  Then again, I'm going to assert things.  The file upload area should be visible, and now the login shouldn't be visible to the  browser.  That's what I'm asserting here.  There are also implicit waits that are available in Selenium, and those just wait for a set  amount of time.  Sometimes writing these assertions on how it should explicitly wait can get a little  tricky, and it's like a cheap fallback to just wait, but if you just say wait for a  second, and you start doing this in a lot of tests, you've added a second to each test  run, and then you've added another second to each test run, and it really starts to  add up.  It can really add useless time to your test running, and these tests aren't particularly  fast to begin with.  Follow the Xenopython, explicit waits are better than implicit waits.  Users aren't always right, so other things you might want to assert with Selenium would  be they fill in their password, but they miss a character, and they should see an error,  and make sure that you're rendering the errors correctly.  These are great end-to-end tests, but they're slow, and they can be fragile.  If you're thinking about how we're building these assertions, we're finding elements by  HTML ID, we're finding elements by class name.  If a front-end developer or designer comes in and tweaks some of the class names, now  this test fails, even though the functionality should still work, my test fails, or if the  error message changed slightly.  You want to use these as they couple well and pair well with other tests in your test  suite.  These are integration-style tests.  Let's talk about pure JavaScript testing.  For edge cases, really tricky logic, it's hard to beat isolated unit tests, and sometimes  this goes out the window when we write JavaScript, because we're primarily Python-focused, but  I feel like the Django community really values testing, and maybe the reason is people don't  know what tools are out there.  Part of why it's hard to know what tools are out there is that in the time I've been talking  about this slide, I think eight more testing frameworks have been written for JavaScript.  I'll tell you about the ones that were in existence when I started my talk.  Some of the popular ones are QUnit, Mocha, Jasmine, Karma, Protractor.  They sound like testing frameworks, and you probably would have Googled Karma and thought,  yeah, definitely, like that Jasmine, that was the one I was thinking of.  So I'm going to save you some trouble and tell you to just use QUnit.  I'm going to tell you why, and if you don't agree with my why, check out one of these other  ones.  I like QUnit because it has a familiar XUnit style, that style that everyone has ripped  off from small talk since the beginning of history.  There are other frameworks that choose to do a more like BDD style, like that lettuce  cucumber style that we would have in Python.  Those would be like Jasmine, and Mocha is actually really weird.  They're like, just choose however you want to write your tests.  You kind of want things to be a little more opinionated, or I like things to be a little  more opinionated.  So Jasmine is a very popular BDD style testing framework.  If you like XUnit, that's a high barrier for some people.  If you like XUnit, use QUnit.  It's used by large projects like jQuery and Trey Hunter.  I don't know if he's here.  Thank you.  I'm going to go through this to Django 19, fantastic work to cover unit tests for the  admin.  Setting up QUnit is about creating a static HTML page which includes all of the QUnit  pieces.  There's some CSS for QUnit, and then there's the QUnit JavaScript itself.  For my project, or the project I'm showing you here, there's the code that we want to  test, which is the models and the views, the backbone models and views, not Django models  and views.  And it uses backbone, so it depends on backbone, which depends on underscore and depends on  jQuery.  So those are all included.  And then at the very bottom, I have two new files.  That's the test models and test views.  I know that the end script tags are missing.  It's like a weird error with reveal.js, which is what I'm using for my slides.  So put your script tags in there.  They're in in the repo.  Our first QUnit test uses QUnit.test, which names the test and then has the test function  itself.  The test function is given a single argument, which is the assertion pieces.  And it has all the assertion APIs you kind of expect from XUnit, like expect or assert  equals or assert okay or assert not okay or not equals.  In this case, and you don't have to know too much about backbone, I hope to at least read  what this test is doing, I'm creating an instance of a backbone model similar to a Django model  and I'm asserting that a method call URL, that that method call returns the URL that  I expect.  That's really it.  So again, pretty standard unit test, create an instance of a class, call one method, assert  one thing, just what you want from a unit testing.  Things you're also probably familiar with from unit testing are things like set up and  tear down.  I mean, we were looking at set up and tear down for Selenium.  They don't call it set up and tear down anymore.  They call it before each and after each.  And it uses a QUnit module.  This is the thing that I hate the most about QUnit.  The module groupings of tests are implicit by ordering.  So any test declared after this is part of the module.  And they're all part of the module until the file ends or until there's another module  declaration, which is clearly not Python.  So, in this case, I want to start testing my backbone views.  So before each test, I'm going to create an instance of that view and I insert it into  the DOM.  And then after each test, I rip it out of the DOM.  So for every test that's going to run, I get a fresh copy of my view that I'm going to  test.  Any state that you attach to this will be available using this inside of any test in  the module.  So any test after this module has access to this.view.  Because everyone understands how JavaScript this works, right?  So here's a test that tests this view.  Again, I want to call a method on my view.  My view has a method called add file and it takes an instance of a model.  So I create an instance of the model, I call the method, and I assert that there's a new  DOM element.  And I could assert more things about that DOM element.  I mean, this is pretty minimal.  I could assert, like, the text of the DOM element or I could have looked at seeing how  the DOM element relates to the model itself.  But there's no user interaction here.  There's no user.  There's no clicks or API calls.  This is just calling methods on objects and asserting what happens when you do them.  As a little bonus, QUnit doesn't come with any mocking, but it does play well with a  library called Scion.js, I think I'm pronouncing that right.  Also super-googleable.  So in this case, I want to mock a thing.  This view, this other view I want to test, actually handles the drag and drop.  So when the file gets dropped, it makes an API call to the server to post.  The server is not running when you load this page, so I need to mock that call.  So in my set up tear down, you know, not set up tear down, but my before each, after each,  I create an instance of the view and then I patch the collections create method.  And again, that's a little bit of backbone to know here, but collection create actually  does that post to the server.  And then a test which might use this is, again, I want to fake like a user did a drag and  drop.  So I create a fake DOM event called drop and then digging deep into how drag and drop works  in JavaScript.  And there's a drag and drop, there's a data transfer and data transfer contains a list  of files that the user is dragging.  But I'm again constructing it like a user just clicked and created this event.  And then I call the drop callback, which would be called in this case, and assert that create  would have been called.  The API call would have been called.  And I can assert, again, more things about what that call would be.  There's some hover state there for the drag and drop and it gets asserted here as well,  but it's not really part of the mocking.  To run Q unit test, you just load that file in your browser and it looks like this.  It highlights all the things as they run, they run usually pretty fast.  There are command line tools to automate this.  You can use your favorite node task runner like grunt or gulp or you can just use phantom  on your own.  Of course, we also know a cheap way to automate things in a browser with Python called Selenium.  So we could just load this file with Selenium and then assert that there weren't any failures  by reading the DOM.  It's kind of a cheap way.  I don't know that I would do this if I had a huge test suite of Q unit, but it lowers  that barrier of entry.  Grunt and gulp are non-trivial things to add to your stack.  You have to really be committed to add them.  So this is a way to sort of get started with Q unit tests and without all those tooling  pieces.  So for this project, when you run them, all these things run.  There are Selenium tests which drive this browser interaction, there are Q unit tests  which cover the client side interaction, or actually aren't any Python unit tests for  all the view code, but those are pretty, those wouldn't be hard to write.  Just too lazy.  But this actually gets, I think, like there's like two or three lines that aren't covered  in this case which are like all the 404 handling.  You ask for a file that doesn't exist.  The user can't do that in the user interface.  The user can't easily do that.  So it's not covered.  Some resources.  So basically the Django documentation on testing is, you know, your first go-to place.  And the Python Selenium bindings also have great descriptions of all those API methods  for waiting on events.  There's different ways to find and detect when the DOM changes.  In my case, as I was waiting for the visibility of an element, but you can wait for the title  of the page to change, you can wait for things to disappear or reappear.  There's lots of different ways that you can use that.  The Q unit docs and the Scion.js docs also very helpful.  Some photos are still from Flickr, Creative Commons.  Thank you very much.  My slides are available on this extraordinarily long link which I'll tweet out.  I didn't use the Libyan DNS bit.ly.com to do it.  But we have a book signing at 2.50 as well.  We actually do have like five minutes.  So I will take questions either now or then or everyone can run and get some lunch or  find out who won the Microsoft contest.  Thank you.  Thank you.
DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_0_47.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_48_143.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_144_287.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_288_407.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_408_791.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_792_1055.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_1056_2123.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_2124_3275.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_3276_3395.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_3396_3683.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_3684_3863.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_3864_4727.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_4728_5687.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_5688_7031.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_7032_7775.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_7776_9935.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_9936_12587.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_12588_14267.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_14268_16163.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_16164_18095.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_18096_19667.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_19668_21407.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_21408_23831.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_23832_25223.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_25224_26795.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_26796_28463.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_28464_29279.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_29280_30383.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_30384_31859.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_31860_33035.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_33036_33215.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_33216_34091.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_34092_34139.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_34140_34199.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_34200_34271.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_34272_34355.png DjangoCon_US_2015_-_Intro_to_Client-Side_Testing_by_Mark_Lavin_5AWuT5hEgRA_34356_34368.png
What does repeat all of the things that you just said? I work for Cactus Group. I'm the co-author of Lightweight Django. You can find me on Twitter. I'm Dr. Oyes. I'm MLAVN on GitHub and the rest of the Internet. If you can help me acquire the MLAVN Twitter account, like come see me after. Be interested. So what's the goal of this talk? The goal of this talk is to get you started writing tests from a practical standpoint. This isn't supposed to be an in-depth review of all available JavaScript testing tools. And I'm not here to convince you to do testing. Like, that argument, I don't think needs to be made anymore, especially not in this community. So the workflow is I'm going to show you a little example project. I'm going to show you some integration tests for that project, and then we'll go through some unit tests for that project. So what are we going to test today? This is a project that lives on file API MLAVN.org, and the source code is available on my GitHub account. It's a minimal REST API using JSON web tokens for auth. It does drag and drop file uploads. There's a limit. Please don't try to upload, like, 100 gigs of my server, please. The front end uses backbone. I'm not going to show you any of the code for the views, any of the backbone code. I'm only going to show you the tests. If you want to take the time and review the project code itself, do so in your own time. It looks like this. You log in, there's a guest log in. When you, after you log in, you see this blob and an empty line because they didn't spend any time styling this. When you drag a file, it's there, and then there's a little X, and you can delete it. That's what the project looks like. On the back end, it looks like this. There's not that many views. There's, you know, the API route which renders the single-page app. There's an endpoint to exchange an API token, the username and password for an API token. You can get uploads to list all uploads. You can post a new upload. You can get uploads, like details for a single upload. You can delete an upload. There's no put for updating. Yeah, it's super restful. But users don't care about rest. Users don't care about how restful the back end is. They want to do things on the site. They want to log into the site. They want to navigate around. They want to submit data. They want to see their data. And so those are the things that you want to test to make sure that this works as expected. And that's what Selenium is really good at. Selenium drives a browser, and you can interact with the DOM. You can fill in data. You can click links. You can assert things about the current state of the DOM. Some basic Selenium setup, this almost comes straight out of the Django docs, is to use the live static server test case and do some setup tear down of the Selenium web driver. The setup and tear down can be a little slow. That's why it's done once per class rather than per test. That means you don't get perfect test isolation, and it can lead to problems. I like Phantom.js for the driver. It's a headless webkit browser. You can install with NPM. You could replace this with Firefox or Chrome or IE, or you may want to run your test against all of them. That's all available options. Phantom.js also works well. It's installed by default on Travis CI. So it integrates well with CI environments. A Selenium test looks something like this. You tell the browser to get a page, and then you assert something about that page. So I say when I get the page, I should see the login form. So I find the login form by its HTML ID, and I look for that little DOM element that's the file upload, and the user should not be able to see that if they haven't logged in. Moving on to that, you can fill out forms. So we have a login form. We know it's there. We find it, and we get the inputs by their name. There's a username input, and there's a password input. And you use send keys to emulate, like, user typing in the form. You can also make this work with, you know, select boxes, file inputs. You can do all those things. You can fill them out. The nice thing about Selenium, and the reason you need to use the static live server test case to get really meaningful tests, is that if you try to do interactions like filling in forms, and that form element, one isn't found, it's going to error. But more so if the form element is in the DOM, but it's not visible to the user, it will error, which can give you something far more meaningful than what, like, Django's test client can give you. Not just that it's in the DOM, but that's actually visible to a user. Then you can submit forms by either submitting the form element or by, you know, we could have found the submit button and hit, clicked it. This is a little helper method. This isn't actually a test itself. A test for this might look something like this. I want to call my little helper to log in, and then I need to wait for the browser. I need to wait until something happens. In a traditional web application, this might be, like, a redirect to another page, and I need to wait for that redirect to happen. In my single-page app, I need to wait for the API call to go to the server and come back, and then the DOM is going to change. I'm asserting how I expect the DOM to change. In this case, I'm going to wait until I can see the upload. It should now be visible. I'm going to wait five seconds. It'll error if it takes more than five seconds for that to happen. Then again, I'm going to assert things. The file upload area should be visible, and now the login shouldn't be visible to the browser. That's what I'm asserting here. There are also implicit waits that are available in Selenium, and those just wait for a set amount of time. Sometimes writing these assertions on how it should explicitly wait can get a little tricky, and it's like a cheap fallback to just wait, but if you just say wait for a second, and you start doing this in a lot of tests, you've added a second to each test run, and then you've added another second to each test run, and it really starts to add up. It can really add useless time to your test running, and these tests aren't particularly fast to begin with. Follow the Xenopython, explicit waits are better than implicit waits. Users aren't always right, so other things you might want to assert with Selenium would be they fill in their password, but they miss a character, and they should see an error, and make sure that you're rendering the errors correctly. These are great end-to-end tests, but they're slow, and they can be fragile. If you're thinking about how we're building these assertions, we're finding elements by HTML ID, we're finding elements by class name. If a front-end developer or designer comes in and tweaks some of the class names, now this test fails, even though the functionality should still work, my test fails, or if the error message changed slightly. You want to use these as they couple well and pair well with other tests in your test suite. These are integration-style tests. Let's talk about pure JavaScript testing. For edge cases, really tricky logic, it's hard to beat isolated unit tests, and sometimes this goes out the window when we write JavaScript, because we're primarily Python-focused, but I feel like the Django community really values testing, and maybe the reason is people don't know what tools are out there. Part of why it's hard to know what tools are out there is that in the time I've been talking about this slide, I think eight more testing frameworks have been written for JavaScript. I'll tell you about the ones that were in existence when I started my talk. Some of the popular ones are QUnit, Mocha, Jasmine, Karma, Protractor. They sound like testing frameworks, and you probably would have Googled Karma and thought, yeah, definitely, like that Jasmine, that was the one I was thinking of. So I'm going to save you some trouble and tell you to just use QUnit. I'm going to tell you why, and if you don't agree with my why, check out one of these other ones. I like QUnit because it has a familiar XUnit style, that style that everyone has ripped off from small talk since the beginning of history. There are other frameworks that choose to do a more like BDD style, like that lettuce cucumber style that we would have in Python. Those would be like Jasmine, and Mocha is actually really weird. They're like, just choose however you want to write your tests. You kind of want things to be a little more opinionated, or I like things to be a little more opinionated. So Jasmine is a very popular BDD style testing framework. If you like XUnit, that's a high barrier for some people. If you like XUnit, use QUnit. It's used by large projects like jQuery and Trey Hunter. I don't know if he's here. Thank you. I'm going to go through this to Django 19, fantastic work to cover unit tests for the admin. Setting up QUnit is about creating a static HTML page which includes all of the QUnit pieces. There's some CSS for QUnit, and then there's the QUnit JavaScript itself. For my project, or the project I'm showing you here, there's the code that we want to test, which is the models and the views, the backbone models and views, not Django models and views. And it uses backbone, so it depends on backbone, which depends on underscore and depends on jQuery. So those are all included. And then at the very bottom, I have two new files. That's the test models and test views. I know that the end script tags are missing. It's like a weird error with reveal.js, which is what I'm using for my slides. So put your script tags in there. They're in in the repo. Our first QUnit test uses QUnit.test, which names the test and then has the test function itself. The test function is given a single argument, which is the assertion pieces. And it has all the assertion APIs you kind of expect from XUnit, like expect or assert equals or assert okay or assert not okay or not equals. In this case, and you don't have to know too much about backbone, I hope to at least read what this test is doing, I'm creating an instance of a backbone model similar to a Django model and I'm asserting that a method call URL, that that method call returns the URL that I expect. That's really it. So again, pretty standard unit test, create an instance of a class, call one method, assert one thing, just what you want from a unit testing. Things you're also probably familiar with from unit testing are things like set up and tear down. I mean, we were looking at set up and tear down for Selenium. They don't call it set up and tear down anymore. They call it before each and after each. And it uses a QUnit module. This is the thing that I hate the most about QUnit. The module groupings of tests are implicit by ordering. So any test declared after this is part of the module. And they're all part of the module until the file ends or until there's another module declaration, which is clearly not Python. So, in this case, I want to start testing my backbone views. So before each test, I'm going to create an instance of that view and I insert it into the DOM. And then after each test, I rip it out of the DOM. So for every test that's going to run, I get a fresh copy of my view that I'm going to test. Any state that you attach to this will be available using this inside of any test in the module. So any test after this module has access to this.view. Because everyone understands how JavaScript this works, right? So here's a test that tests this view. Again, I want to call a method on my view. My view has a method called add file and it takes an instance of a model. So I create an instance of the model, I call the method, and I assert that there's a new DOM element. And I could assert more things about that DOM element. I mean, this is pretty minimal. I could assert, like, the text of the DOM element or I could have looked at seeing how the DOM element relates to the model itself. But there's no user interaction here. There's no user. There's no clicks or API calls. This is just calling methods on objects and asserting what happens when you do them. As a little bonus, QUnit doesn't come with any mocking, but it does play well with a library called Scion.js, I think I'm pronouncing that right. Also super-googleable. So in this case, I want to mock a thing. This view, this other view I want to test, actually handles the drag and drop. So when the file gets dropped, it makes an API call to the server to post. The server is not running when you load this page, so I need to mock that call. So in my set up tear down, you know, not set up tear down, but my before each, after each, I create an instance of the view and then I patch the collections create method. And again, that's a little bit of backbone to know here, but collection create actually does that post to the server. And then a test which might use this is, again, I want to fake like a user did a drag and drop. So I create a fake DOM event called drop and then digging deep into how drag and drop works in JavaScript. And there's a drag and drop, there's a data transfer and data transfer contains a list of files that the user is dragging. But I'm again constructing it like a user just clicked and created this event. And then I call the drop callback, which would be called in this case, and assert that create would have been called. The API call would have been called. And I can assert, again, more things about what that call would be. There's some hover state there for the drag and drop and it gets asserted here as well, but it's not really part of the mocking. To run Q unit test, you just load that file in your browser and it looks like this. It highlights all the things as they run, they run usually pretty fast. There are command line tools to automate this. You can use your favorite node task runner like grunt or gulp or you can just use phantom on your own. Of course, we also know a cheap way to automate things in a browser with Python called Selenium. So we could just load this file with Selenium and then assert that there weren't any failures by reading the DOM. It's kind of a cheap way. I don't know that I would do this if I had a huge test suite of Q unit, but it lowers that barrier of entry. Grunt and gulp are non-trivial things to add to your stack. You have to really be committed to add them. So this is a way to sort of get started with Q unit tests and without all those tooling pieces. So for this project, when you run them, all these things run. There are Selenium tests which drive this browser interaction, there are Q unit tests which cover the client side interaction, or actually aren't any Python unit tests for all the view code, but those are pretty, those wouldn't be hard to write. Just too lazy. But this actually gets, I think, like there's like two or three lines that aren't covered in this case which are like all the 404 handling. You ask for a file that doesn't exist. The user can't do that in the user interface. The user can't easily do that. So it's not covered. Some resources. So basically the Django documentation on testing is, you know, your first go-to place. And the Python Selenium bindings also have great descriptions of all those API methods for waiting on events. There's different ways to find and detect when the DOM changes. In my case, as I was waiting for the visibility of an element, but you can wait for the title of the page to change, you can wait for things to disappear or reappear. There's lots of different ways that you can use that. The Q unit docs and the Scion.js docs also very helpful. Some photos are still from Flickr, Creative Commons. Thank you very much. My slides are available on this extraordinarily long link which I'll tweet out. I didn't use the Libyan DNS bit.ly.com to do it. But we have a book signing at 2.50 as well. We actually do have like five minutes. So I will take questions either now or then or everyone can run and get some lunch or find out who won the Microsoft contest. Thank you. Thank you.
building these assertions, we're finding elements depends on backbone, which depends on underscore little bonus, QUnit doesn't come with any load this file with Selenium and then assert that