Another powerful testing tool in the Sinon library is a stub. Stubs are essentially more powerful spies. They can do anything that a spy can do, and additionally they allow you to control the behavior of a particular function.
We want to use stubs when our program requires us to invoke functions that behave a certain way, either they return data, invoke a callback with data, or even throw an error. Let’s go ahead and incorporate a Sinon stub into our test. Let’s navigate to our exercise files, and in chapter 10, lesson six, under the start folder, I want to take a look at our soot, the order.js.
So this is the module that we’re testing, and we’re specifically testing the order function under this module. If you look at this order function, on line 23 we are actually sending a packageAndShip call to the warehouse.
We send it the sku of an order item, the quantity, and presumably the warehouse is actually going to package and ship that item. They will go find the item on the shelf, put it in a box, ship it, and give us a tracking number back in the callback function that we send to the packageAndShip method.
So we actually don’t want to package and ship any orders when testing the warehouse function, and we also notice that the warehouse function will take some time. When we actually order an item, it takes a little bit of time for this warehouse to package and ship our item. If we have a lot of tests, testing the order function could actually make our test run for a long time. So we’re going to get a double benefit out of mocking out the warehouse and also mapping out the packageAndShip function with a Sinon spy. So let’s go ahead and navigate back to our exercise files, and let’s go into the test folder and open up our order-spec.
And from our order-spec, the very first thing that we need to do is create a mock warehouse. So we will do this inside of this beforeEach hook where we create a fake testData and mock console, we are also going to add a warehouse. And the warehouse is going to have, add my semicolon, the warehouse is going to have a packageAndShip function. And what we are going to use is a Sinon stub. So we’ll use the Sinon stub.
So let me take one last look at my order very quickly. Taking a look at my order and the order function, on line 23 the warehouse.packageAndShip function is expected to invoke my callback, and it’s expected to invoke my callback and send a tracking number. So we can do this with a Sinon stub. We can tell the Sinon stub that we just created that it should invoke this callback and that it should send a fake tracking number to this callback as well. So let’s navigate back to our test.
So if we would like to tell this Sinon stub to invoke the callback function that is sent to the packageAndShip function, we can do so with a yields function. The yields function will invoke the callback that is sent to the packageAndShip function, and we can add the arguments that it should send to that callback. So I’m just going to send a fake tracking number, 10987654321, there we go. So the packageAndShip callback will be yielded with 10987654321 as the fake tracking number.
So the next thing that we need to do is inject our mock warehouse. So we will set the warehouse to this.warehouse. And now when we invoke the packageAndShip function in our test, we won’t be sending the call to the real warehouse, we will be sending the call to our mock warehouse. So let’s go ahead and actually incorporate this in our test. And to do so, I’m actually going to come to the bottom of this, after this it statement, and I’m actually going to nest another test suite here so let me get some space, and I will add a describe.
And we are going to describe the warehouse interaction. And we will actually write several tests within this callback function. And we are going to test two things with the warehouse interaction, that a successful order receives a tracking number. And the other test that we are going to have is that we want to make sure that it calls packageAndShip with the correct quantity, correct sku and quantity.
Because we want to make sure that we are going to be sending the correct data to the real warehouse in production, so we’re going to check the data that we’ve sent to our mock warehouse during the test. Now I’m actually going to execute the order in a hook. So in our beforeEach hook, I’m going to add a callback function. And in this callback function we will write some code that will occur before each of these tests. And what I want to do is go ahead and order an item. I want to actually invoke the orderItem function. And remember we’re using our fake sample data, so we know what data we have.
This is the test data that we set up in this first beforeEach hookup here. This test data is still our fake data, and before every test, this test data gets redeclared so it’s going to be exactly the same. So what we’re going to do is try and order two of item CCC. And what we’re also going to do here is, when this order is successful, we should fire a callback function. But I’m not going to actually add a callback function. What I’m going to do is create a spy so that I can check that my callback function is even called.
So right before this orderItem, I’m going to say this.callback equals a new sinon.spy. So now we can check to make sure that this .callback is actually being called with the correct data because a sinon.spy is the tool that we can use to do that. So under it(“receives a tracking number”); let’s go ahead and write our test. I’m going to add a function here. And in this receives a tracking number, if an order was successful, the callback should have been invoked, and the callback should have been invoked with our fake tracking number because we’re actually sending our order to this packageAndShip stub which is going to invoke our callback with this fake tracking number.
So what we’re going to do to make sure that we’ve received the appropriate tracking number is expect(this.callback), the Sinon stub that we created on line 50 should have been called with, 10987654321, or our fake tracking number. So if our sinon.spy that we declared on line 50 was, in fact, invoked with the correct tracking number, this will equal true. The next thing that we need to check is that the packageAndShip function was actually invoked with the correct quantity.
We need to make sure that we are correctly ordering two of item CCC, like the test declares. So I’m going to go ahead and add another callback function here. And in this callback function, I’m going to expect(this.warehouse.packageAndShip) should have been calledWith(“CCC”), and it should have been calledWith two of those items. So we expect that also to equal true. So if these pass, this means that our order module’s orderItem function is functioning correctly and we can test that without having to involve the warehouse.
So let’s go ahead and save this, and navigate to our terminal, and go ahead and run our test. And it looks like our test has failed. We have an error here. It’s showing us that we have an Unexpected token because we have a semicolon in a literal, so we should probably never do that. I’m going to go ahead and go back to my code. And this problem is in my warehouse mock so when I created the mock here inside of this object, the literal, I probably should have never included a semicolon. That doesn’t belong there. So we’ll go ahead and save this and navigate back out to the terminal.
I will clear this. And let’s try to run our test. So this time, notice that the test passed lightning fast. That’s because we’re no longer involving the real warehouse. So it doesn’t take time to actually package and ship our order, because we’re not packaging and shipping our order. Also notice that all the tests pass. So we’re using our mock warehouse with our stub, and we’re actually making sure that we receive a tracking number, and the packageAndShip is called with the correct sku and quantity. In fact, if I go back to my code, we can actually just change one of these expectations to watch the test fail.
So let’s say that we wanted to make sure that the packageAndShip was called with AAA, as opposed to CCC. I can save that and go out to the terminal, and this time, when I run my tests, they will also fail lightning fast, but it’s showing me that this AssertionError expects false to be true, which means that it wasn’t called with AAA, it was called with CCC. So let’s just go back and change that one last time to the correct sku. And we can go back to our terminal one last time and watch our test pass.
So, great, we have greatly improved our test and we have greatly improved the focus of our test on just the orderItem function. And we’ve also improved the speed of our test by injecting mocks using rewire and Sinon.