Understanding Spy, Stub, and Mock

Welcome back readers! Well, after creating 2 posts explaining about testing on frontend in a row, on this time I’ll also talk about testing! Lol. Actually this time will be more general. However, I applied this knowledge most of cases on frontend so I’ll give an example on frontend side too haha, forgive me plz. Yep, on this time I will talk about testing techniques which are spy, stub, and mock. I was really really starved to know what actually they are and now I want to end my curiosity. I’ll explain to you what actually they are! šŸ˜€

First, our frontend side using JavaScript so I use a library that supports those 3 testing techniques. I decided to use Sinon. I think it’s a powerful library. The installation is pretty simple, just like another node package, you can do:

npm install --save-dev sinon

Anddd tada! Now you can utilize Sinon to help you test frontend stuffs! Even though I use Jest for testing runner, Sinon can work well without any additional setup. What a good start to use it! Now I will tell you about those 3 testing techniques in a simpleĀ way (hopefully).

Spy

Quoting from SinonĀ doc:

A test spy is a function that records arguments, return value, the value of thisand exception thrown (if any) for all its calls.

Looking at the documentation, most of spy API provided to check “calling” such as spy.called that will return true if the spy was called at least once, spy.calledOnce that will return true if the spy was called exactly once, and so on. Basically, spy is used when we want to know whether the object or function is called, or maybe we just care whether the function is called or not with specific argument, or maybe we want to check the function returns an object similar with what we want.

Here the sample block testĀ that I wrote on this project using spy:

test('alerts error when subscribing project failed', () => {
  const spyOnAlert = sinon.spy(window, 'alert');
  sinon.stub($, 'ajax').yieldsTo('error', errorResponse);

  const projectList = shallow(<ProjectList     projects={projects}     watchedProjects={watchedProjects}   />);

  const newProject = watchedProjects[0];
  projectList.instance().handleSubscribeProject(newProject);
  expect(spyOnAlert.called).toBe(true);
  window.alert.restore();
 $.ajax.restore();
});

Actually on block test above I used sinon.spy() and sinon.stub(). Don’t worry about the stub part, I’ll also explain to you later. Just notice the sinon.spy() part first. Well, on that block I only want to test if the ajax function invokes theĀ error callback then an alert will be displayed or not. Therefore, to check whether the alert will be called, I spy on it. The usage is pretty simple, isn’t it? Spy helps you to check especially on hard part(?) of code. You can just spy on it. It’s like the sample above, how can I know whether the alert is called or not? Just spy on it! It will solve your problem šŸ˜€

Stub

Quoting from Sinon doc again:

Test stubs are functions (spies) with pre-programmed behavior.

Uh well, maybe you are still wondering what kind of “pre-programmed behavior” that the stub has. So, when you use stub, actually you can “stub” or maybe it can be said “assign” specific value on the unit that you want. I think the Sinon doc tells us the characteristics of situation that stub is needed, which are:

  1. Control a methodā€™s behavior from a test to force the code down a specific path. Examples include forcing a method to throw an error in order to test error handling.
  2. When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as a XMLHttpRequestor similar).

Yep, like on my sample code on Spy part above, I usedĀ sinon.stub()Ā to stub the ajax function. I stubbed the ajax function then usedĀ .yieldsTo() Ā API to make the ajax function invokes the error callback immediately. First, I used stub since I want to prevent the ajax function being called directly. Second, I used stub to invoke the error callback immediately since on that block test I just care about the error callback. I just want to test when error callback invoked, so I want to create and provide that situation immediately. Well, stub is like an enhanced version of Spy which you can also “assign” a value on specific unit.

Mock

Again, quoting from Sinon doc:

Mocks (and mock expectations) are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations.

Okay, not only “pre-programmed behavior” like a stub has but a mock also has “pre-programmed expectations”. Now, what is “pre-programmed expectations”? Different with stub, when you use mock, you also expect something happened on the mocking unit. Here’s sample block test usingĀ sinon.mock().

test('calls ajax function exactly once when project subscribed', () => {
 const mock = sinon.mock($);
 mock.expects('ajax').once();

const projectList = shallow();

const newProject = watchedProjects[0];
 projectList.instance().handleSubscribeProject(newProject);
 mock.verify();
 mock.restore();
 });

On block test above I mocked the $ from jQuery. Since I mocked a unit, I created at least one expectation according to the mocked unit. Yep, I expected the mocked will call ajax function exactly once. In the end, I have to verify all of my expectation by callingĀ mock.verify(). Ā As you can see, the main difference between mock with spy and stub is mock also has expectations. When you mock an unit, you also expect something from it.

Well, I think this is the end of my post. I hope my explanation about spy, stub, and mock can be understood easily. I also hope this post could help you to differentiate between those 3 testing techniques. Last, bye all! šŸ˜€

 

P.s. Just want to reminder, If you also use Sinon, don’t forget to callĀ .restore()Ā after finish spying, stubbing, or mocking on existing method to unwrap that method. Happy testing! ^^

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s