DEV Community

Snir David
Snir David

Posted on

The difference between mocks and stubs, explained with JS

Stubs and Mocks are two foundational concepts in testing that are often misunderstood. So much so, that we have the famous Martin Fowler article on the subject, alongside numerous stackoverflow questions on the matter.
Martins article is a long read for the modern impatient reader, get somewhat sidetracked and doesn't have example in the current hype language, JS.
I'll try to be more concise in my explanations here.

I'll start with the headline definition:
Stubs and mocks are both dummy objects for testing, while stubs only implement a pre-programmed response, mocks also pre-program specific expectations.

To put it into a workflow:

Stubs

  • Setup - define the stub itself, what object in the program you are stubbing and how
  • Exercise - run the functionality you want to test
  • Verify - check the stub for values that ran through it, that they fit expectations
  • Teardown - if needed, clean up. e.g Time stubs are usually global, you need to yield control back

Mocks

  • Setup object - define the mock, what object you are mocking and how (similar to stubs)
  • Setup expectations - define what you expect will happen to this mock internally
  • Exercise - run the functionality you want to test
  • Verify mock - verify that the mock expectations are met. In some JS libraries this happens automatically without additional call, the mock expectations are verifying themselves and will throw if needed. (Used mostly when async testing).
  • Verify - verify any additional expectations for results on the mock
  • Teardown - if needed, clean up.

Mocks & Stubs in the JS community

Before we get into code, since my examples will be in JS, there is an important note to be said here.

One of the most successful testing libraries in the JS community is jest. But I will not use it for my examples, for the simple reason that jest is opinionated and does not implement mocks.
What they call a mock in the library, is actually a stub by definition. You cannot make expectations on the mock itself, rather just look at its behavior and call and make expectations on that.

I will demonstrate the concept using sinon.js that does implement the concepts of both mocks and stubs.

Stubs example

For our example, we will unit test an imaginary function for purchasing items in an eCommerce site. We will try to pay and get the payment status, and if we are successful we will send a mail.

const purchaseItemsFromCart(cartItems, user) => {
  let payStatus = user.paymentMethod(cartItems)
  if (payStatus === "success") {
    user.sendSuccessMail()
  } else {
    user.redirect("payment_error_page")
  }
}

}
"when purchase payed successfully user should receive a mail" : function() {
  // Setup
  let paymentStub = sinon.stub().returns("success")
  let mailStub = sinon.stub()
  let user = {
    paymentMethod: paymentStub,
    sendSuccessMail: mailStub
  }

  // Exercise
  purchaseItemsFromCart([], user)

  // Verify
  assert(mailStub.called)
}

Mocks example

Now lets do the same, using mocks.

"when purchase payed successfully user should receive a mail" : function() {
  // Setup objects
  let userMock = sinon.mock({
    paymentMethod: () => {},
    sendSuccessMail: () => {}
  })

  // Setup expectations
  userMock.expect(paymentMethod).returns("success")
  userMock.expect(sendSuccessMail).once()

  // Exercise
  purchaseItemsFromCart([], user)

  // Verify mocks
  userMock.verify()
}

Great. When should I use each?

Now this is the interesting question here.
And there is much debate - there is a reason the guys behind jest decided not to implement classic mock functionality.

Mocks can do whatever stubs can do, plus setting expectations directly on the objects they are faking.
This creates readability problems for large tests as well as tendency to start expecting and testing fake objects within the test that are not the sole purpose of the test, making it a white-box test that is too aware of internals.

That is even mentioned as a guideline in sinon documentation for when to use a mock:

Mocks should only be used for the method under test.
In every unit test, there should be one unit under test.

So to not overuse this functionality and create a confusing, and maybe mispurposed test, you should limit your mocks usage in tests to one object.

Or, you know, you could just use jest that took this decision away from you by not implementing these kind of mocks in the first place.

Top comments (3)

Collapse
 
zakwillis profile image
zakwillis

Hello Snir, thanks for this. I really appreciate this article. I have never taken unit testing in js seriously. I only look upon unit testing server side as being important when it is calculations based. However, UT is a vital part of web applications and worth considering.
My understanding of stubs versus mocks is simpler.
I see stubs as representing data objects, possibly settings, or data sources from somewhere else.
Mocks are, like you describe - behaviors. For example, write file. You don't want to actually write a file, but arrange the fake/mock so it can simulate having written a file. These can then be injected into a receiver/consumer where it just does its job.
Can i ask, and perhaps you are not a C# developer, you read about FakeItEasy? It is an interesting approach.
The main challenge I find with languages such as JavaScript and Python is they are dynamic languages potentially making these more of a theoretic exercise than statically typed languages.
Finally, i find Javascript completely exposes intellectual property to the end client, so if the server can do the effort without penalty, i prefer to keep business logic there.
None of this is meant to criticize and again, many thanks.

Collapse
 
snird profile image
Snir David

Thank you for the comment (:
Actually everything I described here still stands in any context, frontend as well as backend testings with nodejs.

Everything you say is right as well, mocks allow you the fake a behaviour of certain object, and to test the behavior in place. This is why it is recommended to just use mock on one object per test unit - this way you make sure you are testing just one behaviour and not looking at some irrelevant internals.

Regarding the JS and Python beingh dynamic languages - I think this is another, super interesting and dense, discussion. About the correctness of languages without static types and how you should test them differently than statically typed languages (if you should do it differently, at all).

Collapse
 
rossmawd profile image
Ross Mawdsley

In your mocks example - should 'userMock' be being passed to purchaseItemsFromCard() instead of 'user' ...which I don't believe exists in this scope?