NEAL KEMP: Hi RailsConf. Today we're going to be talking about testing your services. But before we get into that, I want to introduce myself. My name's Neal. I'm originally from Iowa, so I feel at home here in the Midwest. Now I live in California, in LA specifically, and I'm a software developer and independent consultant who does Rails, JavaScript, HTML, CSS. Basically if you name an acronym I've probably coded in it at some point. So, today's talk is going to be the what, the why, and the how of testing your services. This is not a talk about building testable services. I could do an entire talk about that on its own. It's also not necessarily a talk about test-driven development. While I'm a practitioner, I don't think that the principles applied here correspond directly to test-driven or not test-driven development. [laughter] So. Got that one. Good. So first we're gonna talk about the what. So, we have to ask ourselves, what is a service. So I break it down into two main categories. First of all we have external services, like the Twitter API, Facebook's API, or Amazon Web Services. And the other category is internal software-oriented architecture, a buzz word we all know and love. So, basically for the purpose of this talk, it means anytime you're making an HTTP request to an endpoint in another repository. So, basically any network request you're making outside of your application. So now we're gonna talk about. Cause we need some justification before we go ahead and test all of our services without question. So, first we have to ask ourselves, why are services themselves important? I think a lot of these are pretty self-evident. It allows us to build things faster. It allows us to scale more easily. And we use them on basically every application. I don't think I can personally think of an app I've built in the past few years that hasn't glued on multiple services. And I'm also noticing that we're using more and more services for every application. So I would argue that services are critical to modern Rails development. So we have to ask ourselves then, why is testing those services important? Well, first of all, you should be testing everything else, so why would it be different for services? And services often compose critical features of your application. For example, a stripe integration. If your billing's down, you're gonna have a lot of issues. You know, if you have an API request to S3, you're not gonna be able to serve images, if that's down. And you might also encounter problems with these APIs. I know I sure have. Basically, any time I've worked with an API there's been some unexpected results. So the first example I'm gonna take you through is, you know, an internal API built by consultants in another part of the company. So this is the software-oriented architecture we're talking about. And they were exposing this API for our Rails app to consume, but we had issues all along the way, and it served to increase the project length significantly. Sometimes we'd have random null responses when we were supposed to get objects. There is random inconsistencies where we'd get weird symbols being printed out and different formatting. And in general it was a catastrophe. So it definitely lengthened the time to completion. And this was a lot due to a failure on our part to, you know, test the API thoroughly. So we couldn't express to them, you know, the problems we were having until we put it into production. So this is one problem that could have been solved by testing first. So, now we're gonna talk about a few problems I've had with external APIs. And I'm sure all of you have encountered similar issues with APIs in the past. So do we have any NHL fans in the, in the house here? Yeah? Chicago Black Hawks. Doing pretty well in the play-offs so far. We'll see how they go. I mean, obviously they're gonna get crushed by the Kings in a few rounds here, or the Sharks, possibly. But we'll see. I don't want to start a sports rivalry today. So you know, this basically ranged from small annoyances to, you know, major issues with this API. So we'd have annoyances like this, where some responses would come back with an id for the team, and others would come back with a code. And in this case, both of them refer to Anaheim. So this is a minor annoyance. You can code around that. Here, we have an undocumented bug, where basically the goals were all supposed to be as a part of an array, but if you only had one goal, it would be an object. And sadly we discovered this one in production, during a game. So that wasn't ideal. But, worst of all, after we had gone through all of the trouble of fixing these, we realized that there was no versioning on this API. So, even if we fixed it, we might be fixing it again a week later. So this is basically what it felt like to work with their API. So, another project I worked on, this is just kind of a fun side project, was a Snapchat API Client, so I could, you know, work with the Snapchat private API. And, well, one of these examples is extreme, in that there is haphazard documentation, or no documentation, in this case. I think we've all worked with APIs that have improper documentation. But in this case we didn't even know what the requests were, so we had to figure that out. There's also bizarre obfuscation implanted inside of the app itself that basically encrypted, on their iPhone, so people like me couldn't go in and build things like this. And there's a GitHub link if you're curious. So, now that we've talked a little bit about why it's important and outlined some of the problems you might encounter, we're gonna talk about how you're actually going to test these. So first we need to ask ourselves, what is different about services than regular code that we're testing? Well, first of all, we have external network requests that are being made, and second of all, you don't own the code, so you can't really do unit testing on it. It all has to be done from integration test perspective. So, what I propose for your tests, in general, is that you turn Airplane Mode on. This, I find, is the best way to think about your tests, because, first of all, failure is really bad in testing, and you shouldn't be making any, any network requests. So I think of this kind of in two ways. First of all, so it's Airplane Mode in the test mode so you can't do these things. But also it should be a test that you can run on an airplane. Basically meaning that if you're, you know, on a long trans-Atlantic flight or in the RailsConf lobby, you can still make your tests and, and they won't fail because of network issues. So this means you should not interact with your services from your testing environment. And we have a few caveats which I'll get into now. So, this includes dummy APIs. So there's some API makers that have their real API, and then they have a fake API which you can hit with requests, but it doesn't make any changes to your data. So you can't hit those, because those are somewhere else on the network. But I do allow you to make pre-recorded responses to those end points, and that means you can record them within your test suite, which we'll get into in a bit more detail later. So, for these examples, I'm going to be assuming that you're using Rails, obviously, and that you're using rspec for simplicities sake. So, it's time to stub out these requests. So, when you're stubbing an object, you're basically - for those who don't know - it's basically putting like a fake object in front of that object, so you're hitting that object instead of hitting the real one and, you know, saving time with, like setup processes and stuff like that. And we're doing a similar thing when you're stubbing a request to an endpoint, except we're saving a lot more time when we're doing so, because we don't have to make that additional network request. So there's some libraries that include built-in stubbing. So Typhoeus, if I pronounced that correctly, Faraday and Excon are three examples of pretty widely-used HTTP libraries built on top of net HTTP, I think, that have built-in stubbing functionality. But we can simplify this a little bit and use something called webmock, which I'm sure many of you have worked with in the past, which is a general purpose stubbing library for your test suite, so you don't have to learn each individual library's stubbing methods. So I'll take you through a quick example. Here is basic spec_helper. Nothing really interesting about this, except you have to include disable_net_connect! at the bottom. The rest is boilerplate. So I've highlighted that. And, obviously, with all of these examples, you should be putting the gem in your gem file and bundle installing before you start. So, when you put this in your code for the first time, you'll get a really great error with this, and I really like getting these errors, because it tells me exactly, in my code, where I'm making network requests. So if you're not already doing airplane mode tests, you should just plug this disable_net_connect! in, and then you'll get this error, which will tell you where you're making these network requests. And it also is really handy, and it gives you, actually, the request you're making at the bottom. So you can copy and paste that into your test in order to stub it automatically. And obviously you'll have to collect the body and the headers yourself, if you need to use those as well. So, for the following examples, we're gonna use probably the most, sorry, most simple FacebookWrapper ever invented. Basically, all we're doing here is sending a GET request to Facebook graph, the public API, for a user. And what this does is it just returns, like, very basic Facebook information about you. It has your Facebook username, your name, and an id and a few other fields. And then what we're doing with user_id up at the top is we are just pulling out the value for key_id. So this, all it does is return your Facebook id. Super, super simple. And make sure, since we're putting it in lib, that you require it at some point in your loading. So now we're gonna look at a test for this. So this is a test where we're not making a network request, but we're stubbing it out with webmock. So, at the bottom, you can see we're doing our testing case, and we're setting up an expectation that our user_id is equal to Arjun's user_id. And I'm using Arjun because he was the maker of the Facebook graph API wrapper. And, you can see, now, above, we are stubbing the request, just like you'd stub an object. We're stubbing the method of the HTTP request and then we're send, putting the link as the second argument. Next, we have to set up what it returns, and this is just an HTTP response that we're returning. So we want to put a status. You can set headers, which I generally don't do, but if you're doing any operations with the headers, you should definitely set up these in your responses. And the body, we have a really simple JSON string. I've cut out a few fields for brevity. But you can see it has an id, a first_name, and a username. So, this test will pass, and we're also making no net requests, work requests. So, the reasons it's better is it, first of all, it's faster, and we also aren't getting this intermittent failure that we were talking about earlier from the network request. So, that's a good general way, but there's ways we can also save time with this. So a lot of the really popular libraries for API wrappers also include mock-services within themselves or as an additional library on the side, and they use that for, you know, internal testing with their gems. So I recommend, if, if you can find one, to use this before you can go and use webmock, because it'll save you a lot of time. And I'll take you through a quick example. So we're gonna use Facebook graph mock here. And all we are doing is putting it into spec_helper. We're just including the methods and requiring it. Pretty straightforward. And now we're gonna look at a spec. So, basically, all we're doing is we're wrapping the test case within a wrapper that mocks out the request. So basically, all this one's doing is saying we're sending a git request to Facebook graph back slash Arjun, and then the third argument, in this case, is users/arjun_public, which is where the JSON file of this response is located in the gem. So, you can also specify your own responses, and I'd recommend you do that, because I found, actually, some issues with the Facebook graph mock mocking, like, responses, have some outdatedness in them. So, but this, you know, example, I'm not gonna take you through all of the gems that have this. But this can go to show that there are some benefits that you get from using this. It's already stubbed for you. You don't have to learn the API endpoints in order to use it, and some of these provide prerecorded responses for your use, so you don't have to go out and collect these. So it's just a good way of saving time, if you're using some popular libraries. Next, I'm going to take you through sham_rack, which is one of my more favorite ways of doing this. I kind of find this to be a fun way. Basically what sham_rock does, sorry, sham_rack does is it allows you to mount rack-based apps, which include Rails and Sinatra and others, and it allows you to make requests against these fake apps. So, in this case, we're going to get a little help from Sinatra in order to stub out these endpoints. So, spec_helper, the only thing interesting is that we leave in web-mock. Pretty boring there. But then we get to our fake. So I usually just put this spec/support and then fake whatever it is, in this case fake_facebook. And this just means it'll be loaded when you run your specs automatically. But it won't be, obviously, loaded into your production or staging environments, or development. So, in this case, at the top we can see, we're calling sham_rack, and we're setting up the endpoint which we're hitting against, which in this case is graph dot facebook dot com. And 433 is just specifying that we're using the HTTPS SSL link, and dot Sinatra just means we're going to be passing it in a Sinatra app. So, basically, contained within this block is a Sinatra app, and you can do virtually anything you can do with a regular Sinatra app, which is really cool. So you can just, you're just basically mounting this and testing against it. So, for those of you who don't use Sinatra very much, all we're doing here is specifying with the GET keyword that we're making a GET request to back slash something, and just like Rails, when you rake routes you'll see the parametrization of things with a colon before it. We're doing the exact same thing here with username. So, you'll see, in the middle, in the link we interpolate params username, and that's how you pull that out. So this is essentially just returning a string that is this response. You can obviously spice this up by setting status codes, adding conditionals in here if you need some more dynamic power, and also setting up the headers. And you can also, which I sometimes do this in my testing, is back it with like a small yml database, so you can get some more realistic data than just a simple string. So, that's the response. And. Now, when we're writing our spec for sham_rack, all we're doing is keeping it on this base level. We don't have to wrap it with anything, because it will automatically, in your tests, pick up the fact that you have sham_rack mounted, and it will automatically hit against that endpoint rather than hitting against the network. So, you might ask, why is this better? I think there are a few reasons. First, I find it more dynamic. I find it more expressive as well, and you can really add, you know, as much functionality you need to test your integrations as you want. And you can also back it with yml if you need, you know, some pre-population of, you know, real data. And it's also more readable. Let's go back to this for a second. And, you can see, like, reading through this is a lot easier to parse through, and you know where the API requests are being made to, versus the stubbing we showed in the first example, with web-mock, is a little bit hard to read. So that's why I prefer to use this. So next, we're going to talk about vcr, which is a pretty widely-used gem. And this one has some other benefits that I think are really important to use. Basically it prerecords your responses, and we'll take you through an example. So, spec_helper. The only thing interesting here. We have the vcr configuration block, and all we're doing is setting a cassette library. So that's basically where these responses will be saved. And then we're hooking into web-mock, because that's the stubbing library we're using. So, here's a spec. And as you can see, it's really, really similar to the Facebook graph mock. So basically what this does is, you're wrapping it in a block with vcr. So vcr, what it does, is it goes out to the network and makes the request for you, in your testing environment, and it pulls that response back and saves it, in this case, at Facebook user_arjun. And the nice thing about this is you don't have to go out and collect your own responses, which I find to be pretty tedious and also error prone. But, it also means you don't have to break airplane mode with your tests, because you can run this before, and you can cache all of the JSON responses and play them back in your build. So when you're running it on Travis EI or Circle or whatever you happen to use, you're not gonna break your build because of network failure. You're going to be using these cached responses. And that also just allows you to verify the responses. So, like I mentioned, it's a little error prone. I've tried collecting these responses on my own and, you know, sometimes I copy and paste them wrong and come up with an issue. So this kind of allows you to, like, have a nice programatic way of pulling those in. So, there's also an additional build process you can add. So, for the NHL example I talked about, the problem was there was no versioning. So what you can do is, if you want bonus points, and you are really dependent on an API that doesn't have versioning, you can do some kind of build process or, you know, test setup, where you're basically running it outside of your normal test mode, and you check the casettes for diffs, and verify that the responses are not changed from before. So this can help you avoid versioning issues. So I recommend that if you're using something like NHL API. So, the next one we're gonna briefly talk about is puffing-billy. And, aside from having a really cool name and a nice logo on their GitHub, this is an interesting gem to use. We're not gonna use an example here, but basically what it is is for in-browser requests. So basically if you're having integrations that are browser-based, you can record and reuse. Just like vcr, and use those responses again. So, I don't want you guys to think that all of this has to be done in Ruby, and that you have to use vcr to first record your responses. There's a lot of tools out there that will help you to collect these responses, test API endpoints faster, and I want to share some of those with you. So, Chrome Dev Tools. Has anyone heard of this in here? Yeah. Probably, probably all of you. But this is the first one I'm mentioning because I use it, probably every day. Obviously it gives you a really nice way of viewing responses and requests and resending them. So super useful. I'm not gonna get too far in-depth in that one because I'm assuming most people have worked with it. But it doesn't hurt to mention. So next, Postman. If you want to stay within Chrome. This is an extension you can use. And it basically gives you a user interface around running these requests so that you can have kind of an easier way to play with re, requests and responses. And it allows you save them. It gives you, you know, a time in milliseconds of completion. And this one I think is, I was working on a Tinder API Client for fun, so. That's what these requests are for. So, that one's actually up on my GitHub, too, if you're curious. So, I use that a lot. But if you like to stay command line based, I would recommend HTTPie. It's basically an easier-to-use version of curl, and it doesn't have, like, quite the archaic syntax curl has. So, I think it's, you know, worthy. Worthy of use. And, you know, it'd be easier, obviously, you know, to run a script around this than it would be to run it around Postman. So if you need to do something more programatic, this is probably your best option. And, one last tool I really like to use is called Charles. And Charles does a lot of the same things as Chrome Dev Tools does, but it acts as a proxy. So it basically captures, you know, requests between you and your network. So you can set this up to capture any request from your Mac machine, or you can proxy in your phone as well. So I found this really valuable when I was testing out the request from the Snapchat client, because it allowed me to see what my phone was making for requests and record those. And especially when we didn't know what the request was, it was very helpful in that case. And it's also cool because, you know, when you're building an API on Ruby and you want to build on iOS client with it, and you're not really sure how often to pull and stuff, I sometimes pull this up, and I'll just see what other apps are doing. So it's a good way of debugging other peoples work and, you know, seeing how they're doing it well. So, I highly recommend you check it out. It's pretty easy to use, and you can use it with SSL requests as well. So, here's some additional reading. I know you won't have time to write this all down. I'll post the slides on my Twitter. But, next up, let's bring it all together. So, we went over the what, the why, and the how of testing services. So, we've shown that testing services is crucial. They make up really important parts of your app, so skipping tests is pretty dangerous. I'd have to say, if you're in doubt, stub it out. Determine, when you're making choices between, you know, web-mock, sham_rack, or puffing-billy, even, you want to determine the amount of flexibility the need and the amount of extra work you're going to have. For example, it probably takes more time to make a sham_rack server and have dynamic responses than copying and pasting the request you get from the web-mock error. So, you kind of just need to look at the project you have and determine what use case best fits these options. And, also, record responses to save time. I wish I would have started doing this sooner. It's, like, super useful. I would highly recommend you do that. And next up, after me, I'd recommend you stick around. I had the pleasure of pairing with Austin yesterday, and I think his talk plays off my talk a lot, in that it talks a lot about inconsistent test failures. And he goes a lot more in-depth on, you know, other kinds of inconsistent test failures. And you just should definitely stick around if you have the time. So that's it for today. Thank you for taking the time to listen to my talk. And if I don't get to your question, feel free to shoot me an email, or if you just want to chat. And you can also find me on Twitter. So thanks a lot guys.