课程: Practice It: Go REST API Server
Integration tests
- Testing is important for having confidence when maintaining or updating your code. With tests, you can ensure that your code will perform in the same way that you wanted it, especially in cases when new code is added. If you update your code in a way that shouldn't change the operation of your application or library, running your test should confirm that for you. If you are changing the way the code operates, or adding new functionality, then you should update and add more tests. The first type of test is the unit test, which like it sounds, tests units of code, like each method of the product handler, for example. This way if you update one of the handlers, the test should still pass, unless you fundamentally change the way they handler operates. Then you should update the test. In these cases, you want to make sure you cover all scenarios, including any error scenarios, to ensure that your code will fail gracefully. The next type of test is the integration test, which will test end-to-end execution of your code. Bearing in mind that you will have to mock some parts if it calls other services, APIs, or makes a database call. Again, you want to be sure to cover all scenarios including error scenarios. If you find that your tests fail in any of these scenarios including not handling any errors gracefully, you then go back and fix your code. It's also a good idea to run these tests when merging to your repo, so that you won't be able to merge if any of these tests fail. I'll walk you through creating some unit tests. All of the unit tests collectively will make up your integration tests. Okay, so I've already created the backend_test.go. Any file that has _test.go will be considered a test file and will run when you type in go test. So here we're going to import some libraries, the first of which is OS and then we'll need testing and then we'll have bytes encoding for json, and of course our backend library that we will be testing. Also need log, net http, net http, http test, and then we'll also need a string conversion library. Okay, so the first thing we're going to want to do is instantiate the backend application. So we have var a, which will be backend.app. And then the first thing that we'll want to do is have our main test. So we'll have function test main and we'll add testing.M. And then in here what we're going to do is instantiate the application variable. We have app, and then next we're going to call initialize as we would normally. Then we want to do is ensure that our table exists. So we're going to have ensure table exists. But we don't have that function so we're going to add it. So we'll have function ensure table exists. I'm going to give you an example of a couple of unit tests using the product handler and the main app. So for ensure table exists, we're going to add if_ 'cause we don't want anything returned, we're just going to check for an error. And then we have a.DB.execute table product creation query. Now we don't have that defined yet, but we'll define that in one second. And then we will check if the error exists, and if it doesn't we will log a fatal error. But we need to create this. So we will create that as a constant. We'll put that up here. And it will equal... we'll have create table if not exists products, and it will basically recreate the way it is defined in the database. So we'll have id INT not null primary key autoincrement. And next is product. Product code varchar 25. And this is also not null. Then we have name. That is also not null. And then we have inventory INT not null. Price, which is also INT not null. And lastly we have status, which is varchar and not null. So getting back to our test main, after we have table, ensure that the table exists, we're then going to check code to make sure that it works. M.run. And then we're going to want to clear the product table for any other test that we might do. We haven't created that but we'll create that in a second. And we have OS exit after that, the code. But we need the clear product table. So we'll have it here, we'll have... and in there we will a.DB.execute, delete from products. And then we want to reset the autoincrement. So in this case we will do delete, delete from sqlite sequence where name equals products. Next, let's test some of the actual handlers, some of the handler functions. So what we'll test first is test get nonexistent product, and that's t, testing.t. Whoops, should be lowercase. And the first thing we're going to want to do is clear the product table. And then we're going to have the request. We're not going to worry about the error. And it'll be http.new request and it'll be a get. And then since we're testing the existence or the non-existence of our product, the end point will be product. And we're going to look for number 11. And then we add nil, then we'll have response equals execute request. Now we don't actually have this defined yet, we'll define this after we finish this, and then after the response, we're going to check response code and we'll send in t. And the response code should be http status internal server error. Now I know this because I chose all of my errors to be status internal server error. But if you had say used status not found, then that's what you would check against here. And then we have response.code. And also check response code hasn't been defined, so we'll define that also. And then we have var m map because we're going to check the actual returned error code, error string, which is packaged like this. And then we're going to json unmarshal into so we want the response body, and we'll change it into bytes and we'll unpack it into our new variable. Then we're going to check to make sure that it's correct. So we have if m error not equaling sql no rows in result set. 'Cause we just sent back what sqlite sends us back. We don't actually change it to be something else, which you could. And then we have the error. If it doesn't match, we're going to output to the terminal that this is why. And then we're going to output what we got instead. Okay, now we're going to add those two functions we were missing. So first up is execute request. And then that one it takes the request, which is a http request and the output will be response recorder. And then inside of that we will instantiate our variable http.new recorder and we have a .router. And then the request, then we return that. Next up is the check response function. Check response code takes the testing, expected and actual which are both ints. And then what we do is if expected doesn't equal actual then we're going to output that and get expected and then expected actual. Now before we run this, I'm going to add one more, which is test create product. So we'll put that above these since those are utility ones. So we'll add it in here, function test create product. Okay. And the first thing we're going to want to do is clear the product table, and then we're going to create our payload that we would normally get in the post body. So in this case it'll be a byte. Then we add the json, we add product code which will be test 1, 2, 3, 4, 5. The next is name. The name will be product test and the next is inventory. And inventory will be 1. Then price will also be 1. It's just a test so it doesn't have to be real. And status, which will be equal to testing. Okay. Then we'll have request, don't care about the error, and we create a new request and that it's going to be a post. Post products and then bytes, .new buffer, and the payload. Next we'll get the response by executing the request. We'll check the response code and it should be http.status. Okay. Oops. And we'll send in the response code. Now we're going to check that the field values match what was sent in. So we have var, let's add a space here, var m map string interface. Then we're going to unmarshal the response because we do, after creating the product, we do send back the created product. So we're going to do response.body.bytes and we're going to unpack it into our map and then we're going to check them. So we have, if m product code doesn't equal what we said it to be, which was test 1, 2, 3, 4, 5, then we're going to output the error expected product code to be 1, 2, 3, 4, 5 got percent v, and we'll output what we actually received. Okay. So we have that. What I'm going to do is copy this 'cause we have 1, 2, 3, 4 more of those. So we'll just copy them and then we'll update them. So do this, name, inventory, price, and status. Okay, so we have, we'll update this one to be name. And the name was supposed to be product test. So we were expecting product test and we expected name. And this one should be inventory. Now it actually comes back as 1.0. That's why we changed it to an INT in the actual handler before setting the id. It comes back as 1.0 also. But we'll get to that. So then we have 1 and that's what we got. Okay. So we expected inventory to be 1, but what we got was something else. We expect price. And then we expected status. Oh, spelled status wrong. Expected status to be, I believe, testing. Yes. I'm going to put that here. And then we also want to test that the id created in this case should be 1. So we'll copy this. Because this should be the only thing. Since we cleared the table, it should be the only thing in the table. So we have id, and like I was saying before it comes back as 1.0, that's why we changed it to an INT when we assigned it. So we have id should be 1 but we got something else. Okay, so then we're going to- I saved it, and now we're going to try it out. Always love running go run tidy in case something needs to be updated. Oh, I'm not in that directory. And then to run the test suite, you just run, go, test. Hmm. And I failed because I have some errors. One being I can't spell unmarshal somewhere. 58. Ah, would help. That's one. And I'm spelling error f wrong. This should be, let's just search for it, it should be errorf. And then we'll just replace it everywhere. And what was the final error? Response recorder. How? Because I didn't give it the right- that's in line 102. It's http test, 'cause it's a mock. And this also is a mock, so that should also have been test. Okay, let's try it again. Oh, we didn't need it in this, on all of these yet. So I'm going to remove this one. Oh, and there we go. It all worked.