Home >Web Front-end >JS Tutorial >JavaScript Testing Tool Showdown: Sinon.js vs testdouble.js

JavaScript Testing Tool Showdown: Sinon.js vs testdouble.js

Lisa Kudrow
Lisa KudrowOriginal
2025-02-16 11:29:09949browse

JavaScript Testing Tool Showdown: Sinon.js vs testdouble.js

JavaScript Testing Tool Showdown: Sinon.js vs testdouble.js

When unit testing real-world code, many situations make the test difficult to write. How to check if a function is called? How to test Ajax calls? Or use the code of setTimeout? At this time, you need to use to test the stand-in - replace the code to make it easier to test within the hard-to-test.

For many years, Sinon.js has been the actual standard for creating test stand-ins in JavaScript testing. It is an essential tool for any JavaScript developer who writes tests, because without it, it is nearly impossible to write tests for real applications.

Recently, a new library called testdouble.js is emerging. It has a similar feature set to Sinon.js, except that there are a few differences here and there.

In this article, we will explore what Sinon.js and testdouble.js provide and compare their respective pros and cons. Is Sinon.js still a better choice, or can the challenger win?

Note: If you are not familiar with testing stand-ins, it is recommended that you read my Sinon.js tutorial first. This will help you better understand the concepts we will discuss here.

Key Points

  • Feature Sets and Terminology: Sinon.js provides a traditional set of test stand-ins, including spies, stubs, and mocks, which are familiar to those who are proficient in other programming languages, while testdouble.js simplifies terminology and focuses on it In terms of JavaScript-centric vocabulary, traditional spies are eliminated.
  • Easy to use: Testdouble.js has a simpler API that is easier for beginners to master, while Sinon.js provides a more detailed but more complex interface that users familiar with similar test tools in other languages ​​may prefer it to others .
  • Function of processing input: Sinon.js allows for more flexibility in stubs and assertions to use input parameters without necessarily requiring exact matches, which contrasts with the strict requirement of precise parameter matching by testdouble.js unless explicitly configured.
  • Support for Promise and callbacks: Testdouble.js integrates built-in support for Promise and provides powerful callback handling, which may be more suitable for modern JavaScript development than Sinon.js, which requires additional information. Only by configuration can similar functions be achieved.
  • Module Replacement Function: Testdouble.js supports replacing the entire module, which is conducive to isolating test module interactions; this function is not available in Sinon.js unless other tools such as proxyquire or rewire are used.
  • Cleaning and Maintenance: Testdouble.js simplifies the cleaning process with a single reset function, reducing the risk of remaining test stand-ins that may affect other tests, while Sinon.js provides multiple cleaning methods that can be more troublesome to manage. .

The terms used in this article

To ensure easy understanding of what is being discussed, here is a quick overview of the terms used. These are the definitions of Sinon.js and may vary slightly elsewhere.

  • Testing stand-in is a replacement for the functions used during testing. It can refer to any of the three types mentioned below.
  • Spy is a test stand-in that allows the effect to be checked without affecting the behavior of the objective function.
  • Stub is a test stand-in that replaces the behavior of the objective function with something else, such as the return value.
  • Simulation is a different approach than stubs. The simulation contains built-in validation, which can be a substitute for separate assertions.

It should be noted that one of the goals of testdouble.js is to reduce confusion between this term.

Overview of Sinon.js and testdouble.js

Let's first look at the basic usage comparison between Sinon.js and testdouble.js.

Sinon has three different test stand-in concepts: spy, stubs, and mocks. The idea is that each represents a different usage scenario. This makes the library more familiar to people from other languages ​​or who have read books using the same terminology, such as xUnit testing mode. But on the other hand, these three concepts may also make Sinon more difficult to understand when first used. This is a basic example of Sinon usage:

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var spy = sinon.spy(Math, 'abs');

Math.abs(-10);

console.log(spy.firstCall.args); // 输出:[ -10 ]
spy.restore();

// 以下是控制函数执行方式的方法:
var stub = sinon.stub(document, 'createElement');
stub.returns('not an html element');

var x = document.createElement('div');

console.log(x); // 输出:'not an html element'
stub.restore();</code>

In contrast, testdouble.js chooses a simpler API. Instead of using concepts like spies or stubs, it uses languages ​​that JavaScript developers are more familiar with, such as td.function, td.object and td.replace. This makes testdouble potentially easier to get started and more suitable for certain tasks. But on the other hand, some more advanced uses may simply not be possible (which is sometimes intentional).

The following is how to use testdouble.js:

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var abs = td.replace(Math, 'abs');

Math.abs(-10);

var explanation = td.explain(abs);
console.log(explanation.calls[0].args); // 输出:[ -10 ]

// 以下是控制函数执行方式的方法:
var createElement = td.replace(document, 'createElement');
td.when(createElement(td.matchers.anything())).thenReturn('not an html element');

var x = document.createElement('div');
console.log(x); // 输出:'not an html element'

// testdouble 使用一次调用重置所有测试替身,无需单独清理
td.reset();</code>

testdouble uses a simpler language. We "replace" the function instead of "stub" it. We require the testdouble "interpretation" function to get information from it. Apart from that, it's quite similar to Sinon so far.

This also extends to creating "anonymous" test stand-in:

<code class="language-javascript">var x = sinon.stub();</code>

and

<code class="language-javascript">var x = td.function();</code>

Sinon's spies and stubs have attributes that provide more information about them. For example, Sinon provides attributes such as stub.callCount and stub.args. In the case of testdouble, we get this information from td.explain:

<code class="language-javascript">// 我们也可以为测试替身命名
var x = td.function('hello');

x('foo', 'bar');

td.explain(x);
console.log(x);
/* 输出:
{
  name: 'hello',
  callCount: 1,
  calls: [ { args: ['foo', 'bar'], context: undefined } ],
  description: 'This test double `hello` has 0 stubbings and 1 invocations.\n\nInvocations:\n  - called with `("foo", "bar")`.',
  isTestDouble: true
}
*/</code>

One of the biggest differences is related to how stubs are set and verification is done. With Sinon, you can link commands after the stub and use assertions to verify the result. testdouble.js just show you how you want to call a function—or how to "rehearse" a function call.

<code class="language-javascript">var x = sinon.stub();
x.withArgs('hello', 'world').returns(true);

var y = sinon.stub();
sinon.assert.calledWith(y, 'foo', 'bar');</code>

and

<code class="language-javascript">var x = td.function();
td.when(x('hello', 'world')).thenReturn(true);

var y = td.function();
td.verify(y('foo', 'bar'));</code>

This makes the testdouble's API easier to understand, because you don't need to know what actions can be linked at when.

Compare common test tasks in more detail

At a high level, both libraries are quite similar. But what about common testing tasks you might need to perform in a real project? Let's see where some differences begin to appear.

testdouble.js No spy

The first thing to note is that testdouble.js does not have the concept of "spy". While Sinon.js allows us to replace function calls to get information from them while preserving the default behavior of the function, this is simply not possible in testdouble.js. When you replace the function with testdouble, it always loses its default behavior.

But this is not necessarily a problem. The most common usage of spies is to use them to verify that callbacks are called, which is easy to accomplish with td.function:

<code class="language-javascript">var spy = sinon.spy();

myAsyncFunction(spy);

sinon.assert.calledOnce(spy);</code>

and

<code class="language-javascript">var spy = td.function();

myAsyncFunction(spy);

td.verify(spy());</code>

While it's not a big deal, it's still important to note this difference between the two libraries, otherwise you might be surprised if you expect to be able to use spies in testdouble.js in some more specific way.

testdouble.js requires more precise input

The second difference you will encounter is that testdouble is stricter on input.

Sinon's stubs and assertions allow you to be inaccurate with the provided parameters. This is easiest to illustrate by example:

<code class="language-javascript">var stub = sinon.stub();
stub.withArgs('hello').returns('foo');

console.log(stub('hello', 'world')); // 输出:'foo'

sinon.assert.calledWith(stub, 'hello'); // 没有错误</code>

and

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var spy = sinon.spy(Math, 'abs');

Math.abs(-10);

console.log(spy.firstCall.args); // 输出:[ -10 ]
spy.restore();

// 以下是控制函数执行方式的方法:
var stub = sinon.stub(document, 'createElement');
stub.returns('not an html element');

var x = document.createElement('div');

console.log(x); // 输出:'not an html element'
stub.restore();</code>

By default, Sinon does not care how many extra parameters are provided to the function. Although it provides functions such as sinon.assert.calledWithExactly , it is not recommended as the default value in the documentation. Functions like stub.withArgs also do not have "exactly" variants.

Testdouble.js, on the other hand, requires the specified exact parameters by default. This is by design. The idea is that if the function is provided with some other parameters that are not specified in the test, this may be an error and should fail the test.

Arbitrary parameters can be specified in testdouble.js, but this is not the default value:

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var abs = td.replace(Math, 'abs');

Math.abs(-10);

var explanation = td.explain(abs);
console.log(explanation.calls[0].args); // 输出:[ -10 ]

// 以下是控制函数执行方式的方法:
var createElement = td.replace(document, 'createElement');
td.when(createElement(td.matchers.anything())).thenReturn('not an html element');

var x = document.createElement('div');
console.log(x); // 输出:'not an html element'

// testdouble 使用一次调用重置所有测试替身,无需单独清理
td.reset();</code>

Use ignoreExtraArgs: true, the behavior is similar to Sinon.js.

testdouble.js has built-in Promise support

While using Sinon.js's Promise is not complicated, testdouble.js has built-in methods to return and reject Promise.

<code class="language-javascript">var x = sinon.stub();</code>

and

<code class="language-javascript">var x = td.function();</code>

Note: You can use sinon-as-promised to include similar convenient functions in Sinon 1.x. Sinon 2.0 and newer versions include Promise support in the form of stub.resolves and stub.rejects.

The callback support for testdouble.js is more powerful

Sinon and testdouble both provide an easy way to make the stub function callbacks. However, there are some differences in how they work.

Sinon uses stub.yields to make the stub call the first function received as a parameter.

<code class="language-javascript">// 我们也可以为测试替身命名
var x = td.function('hello');

x('foo', 'bar');

td.explain(x);
console.log(x);
/* 输出:
{
  name: 'hello',
  callCount: 1,
  calls: [ { args: ['foo', 'bar'], context: undefined } ],
  description: 'This test double `hello` has 0 stubbings and 1 invocations.\n\nInvocations:\n  - called with `("foo", "bar")`.',
  isTestDouble: true
}
*/</code>
testdouble.js defaults to Node style mode, where the callback is assumed to be the

last parameter. You don't have to specify it when rehearsing the call:

<code class="language-javascript">var x = sinon.stub();
x.withArgs('hello', 'world').returns(true);

var y = sinon.stub();
sinon.assert.calledWith(y, 'foo', 'bar');</code>
What makes the callback support of testdouble more powerful is that you can easily define the behavior of scenarios with multiple callbacks or different orders of callbacks.

Suppose we want to call

callback1

<code class="language-javascript">var x = td.function();
td.when(x('hello', 'world')).thenReturn(true);

var y = td.function();
td.verify(y('foo', 'bar'));</code>
Note that we pass

as a parameter to the function in td.callback. This tells testdouble which parameter we want to use as the callback. td.when

Using Sinon, you can also change behavior:

<code class="language-javascript">var spy = sinon.spy();

myAsyncFunction(spy);

sinon.assert.calledOnce(spy);</code>
In this case, we use

instead of callsArgWith. We have to provide a specific index of the call to make it work, which can be a bit cumbersome, especially on functions with many parameters. yields

What if we want to call

two callbacks with certain values?

<code class="language-javascript">var spy = td.function();

myAsyncFunction(spy);

td.verify(spy());</code>
With Sinon, this is simply impossible. You can link multiple calls to

, but it will only call one of them. callsArgWith

testdouble.js has built-in module replacement

In addition to being able to replace functions with

, testdouble also allows you to replace the entire module. td.replace

This is mainly useful if you have a module that directly exports the functions that need to be replaced:

<code class="language-javascript">var stub = sinon.stub();
stub.withArgs('hello').returns('foo');

console.log(stub('hello', 'world')); // 输出:'foo'

sinon.assert.calledWith(stub, 'hello'); // 没有错误</code>
If we want to replace it with testdouble, we can use

, for example...td.replace('path/to/file')

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var spy = sinon.spy(Math, 'abs');

Math.abs(-10);

console.log(spy.firstCall.args); // 输出:[ -10 ]
spy.restore();

// 以下是控制函数执行方式的方法:
var stub = sinon.stub(document, 'createElement');
stub.returns('not an html element');

var x = document.createElement('div');

console.log(x); // 输出:'not an html element'
stub.restore();</code>

Although Sinon.js can replace member functions of an object, it cannot replace modules like this. To do this when using Sinon, you need to use another module, such as proxyquire or rewire.

<code class="language-javascript">// 以下是查看函数调用的参数的方法:
var abs = td.replace(Math, 'abs');

Math.abs(-10);

var explanation = td.explain(abs);
console.log(explanation.calls[0].args); // 输出:[ -10 ]

// 以下是控制函数执行方式的方法:
var createElement = td.replace(document, 'createElement');
td.when(createElement(td.matchers.anything())).thenReturn('not an html element');

var x = document.createElement('div');
console.log(x); // 输出:'not an html element'

// testdouble 使用一次调用重置所有测试替身,无需单独清理
td.reset();</code>

Another thing you need to note about module replacement is that testdouble.js will automatically replace the entire module. If it is a function export like the example here, it replaces the function. If it is an object containing multiple functions, it replaces all of them. Constructors and ES6 classes are also supported. Both proxyquire and rewire require that you specify individually what and how to replace.

testdouble.js missing some helper functions for Sinon

If you use Sinon's emulation timer, emulation XMLHttpRequest, or emulation server, you will notice that they do not exist in the testdouble.

The emulation timer can be used as a plugin, but the XMLHttpRequests and Ajax functions need to be handled in different ways.

A simple solution is to replace the Ajax function you are using, e.g. $.post:

<code class="language-javascript">var x = sinon.stub();</code>

It is easier to clean up test content using testdouble.js

A common stumbling block for beginners in Sinon.js is often clean up spies and stubs. Sinon offers three different ways to do this, which doesn't help much.

<code class="language-javascript">var x = td.function();</code>
or:

<code class="language-javascript">// 我们也可以为测试替身命名
var x = td.function('hello');

x('foo', 'bar');

td.explain(x);
console.log(x);
/* 输出:
{
  name: 'hello',
  callCount: 1,
  calls: [ { args: ['foo', 'bar'], context: undefined } ],
  description: 'This test double `hello` has 0 stubbings and 1 invocations.\n\nInvocations:\n  - called with `("foo", "bar")`.',
  isTestDouble: true
}
*/</code>
or:

<code class="language-javascript">var x = sinon.stub();
x.withArgs('hello', 'world').returns(true);

var y = sinon.stub();
sinon.assert.calledWith(y, 'foo', 'bar');</code>
Usually, it is recommended to use sandbox and

methods, otherwise it is easy to accidentally leave stubs or spies, which may cause problems with other tests. This can lead to hard-to-trace cascade failures. sinon.test

testdouble.js only provides a way to clean up and test the stand-in:

. The recommended method is to call it in the td.reset() hook: afterEach

<code class="language-javascript">var x = td.function();
td.when(x('hello', 'world')).thenReturn(true);

var y = td.function();
td.verify(y('foo', 'bar'));</code>
This greatly simplifies the setup of the test stand-in and clean up after testing, reducing the possibility of difficult-to-track errors.

Pros and cons

We have now understood the functionality of these two libraries. They both offer a fairly similar set of features, but they have different design ideas from each other. Can we break it down into advantages and disadvantages?

Let's talk about Sinon.js first. It provides some more additional features than testdouble.js and some aspects of it are easier to configure. This provides some higher flexibility for it in more special testing scenarios. Sinon.js also uses the language of people who are more familiar with other languages—concepts such as spies, stubs and mocks exist in different libraries and has also been discussed in test-related books.

The disadvantage is that it increases complexity. While its flexibility allows experts to do more things, it means that certain tasks are more complex than in testdouble.js. It may also have a steeper learning curve for those new to test the concept of a stand-in. In fact, even someone as familiar with it as me may have a hard time explaining in detail some of the differences between

and sinon.stub! sinon.mock

testdouble.js selected a simpler interface. Most of its content is fairly simple and easy to use and feels better suited to JavaScript, while Sinon.js sometimes feels like it's designed for other languages. Thanks to this and some of the design principles, it is easier for beginners to get started, and even experienced testers will find many tasks easier to complete. For example, testdouble uses the same API to set up test stand-ins and verify results. It may also be less prone to errors due to its simpler cleaning mechanism.

testdouble The biggest problem is caused by some of its design principles. For example, a complete lack of spies may make it impossible for some people who prefer to use spies over stubs. This is largely a question of opinion and you may not find the problem at all. Besides that, although testdouble.js is an updated library, it is providing some serious competition for Sinon.js.

Compare by function

The following is a comparison by function:

功能 Sinon.js testdouble.js
间谍
存根
延迟存根结果
模拟 1
Promise 支持 是(在 2.0 中)
时间辅助函数 是(通过插件)
Ajax 辅助函数 否(改为替换函数)
模块替换
内置断言
匹配器
自定义匹配器
参数捕获器 2
代理测试替身
  1. testdouble.js Technically speaking There is no simulation like that in Sinon.js. However, since the simulation in Sinon is essentially an object that contains stubs and validation, similar effects can be achieved by using td.replace(someObject) .
  2. can be achieved similar effects as parameter capturers by using stub.yield (not to be confused with stub.yields).

Summary and Conclusion

Sinon.js and testdouble.js both provide a fairly similar set of features. In this regard, neither is obviously superior.

The biggest difference between the two is their API. Sinon.js is probably a little bit longer and offers many options on how to do things. This may be its pros and cons. testdouble.js has a leaner API, which makes it easier to learn and use, but due to its more arbitrary design, some may find it problematic.

So which one is suitable for me?

Do you agree with the design principles of testdouble? If so, then there is no reason not to use it. I've used Sinon.js in many projects and I can safely say that testdouble.js does at least 95% of the work I've done in Sinon.js and the remaining 5% may be done with some simple workaround.

If you find Sinon.js difficult to use, or are looking for a more "JavaScript-style" testdouble.js might be for you as well. Even people like me who spend a lot of time learning to use Sinon, I tend to suggest trying testdouble.js and see if you like it.

However, some aspects of testdouble.js may cause headaches for those with knowledge of Sinon.js or other experienced testers. For example, the complete lack of spies may be the decisive factor. Sinon.js is still a good choice for experts and those who want the most flexibility.

If you want to learn more about how to use test stand-ins in practice, check out my free Sinon.js guide. While it uses Sinon.js, you can also apply the same techniques and best practices to testdouble.js.

Are there any problems? Comment? Are you already using testdouble.js? After reading this article, would you consider giving it a try? Please let me know in the comments below.

This article was reviewed by James Wright, Joan Yin, Christian Johansen and Justin Searls. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!

Frequently Asked Questions about JavaScript Test Tools: Sinon.js vs. Testdouble.js

What is the main difference between Sinon.js and Testdouble.js?

Sinon.js and Testdouble.js are both popular JavaScript test libraries, but they have some key differences. Sinon.js is known for its rich feature set, including spies, stubs, and simulations, as well as utilities for emulating timers and XHR. It is a versatile tool that can be used in conjunction with any testing framework. On the other hand, Testdouble.js is a minimalist library that focuses on providing a simple and intuitive API for testing stand-ins, which are alternatives to the parts of the system to be tested. It does not include utilities for emulating timers or XHRs, but it is designed to be easy to use and understand, so it is a great option for those who prefer a leaner method of testing.

How to install Sinon.js and Testdouble.js?

Sinon.js and Testdouble.js can be installed through npm (Node.js package manager). For Sinon.js, you can use the command npm install sinon. For Testdouble.js, the command is npm install testdouble. After installation, you can use const sinon = require('sinon') and const td = require('testdouble') to introduce them in your test files, respectively.

Can I use Sinon.js and Testdouble.js at the same time?

Yes, Sinon.js and Testdouble.js can be used simultaneously in the same project. They are all designed to be very neatly simple and work well with other libraries. However, remember that they have overlapping features, so using them at the same time can lead to confusion. It is usually recommended to choose one of them based on your specific needs and preferences.

How to create a spy using Sinon.js and Testdouble.js?

In Sinon.js, you can use sinon.spy() to create spies. This function returns a spy object that records all calls made to it, including parameters, return values, and exceptions. In Testdouble.js, you can create spies using td.function(). This function returns a test stand-alone function that records all calls, including parameters.

How to create stubs using Sinon.js and Testdouble.js?

In Sinon.js, you can create stubs using sinon.stub(). This function returns a stub object that behaves like a spy, but it also allows you to define its behavior, such as specifying a return value or throwing an exception. In Testdouble.js, you can create stubs using td.when(). This function allows you to define your behavior when calling a test standby with a specific parameter.

How to use Sinon.js and Testdouble.js to verify spies or stubs?

In Sinon.js, you can use methods such as spy.called, spy.calledWith(), and spy.returned() to verify spies or stubs. In Testdouble.js, you can use td.verify() to assert whether the test standby is called in some way.

What are the advantages of using Sinon.js instead of Testdouble.js?

Sinon.js has a more comprehensive feature set compared to Testdouble.js. It includes utilities for emulating timers and XHR, which is very useful for testing certain types of code. It is also used more widely and has a larger community, which means more resources and support can be obtained.

What are the advantages of using Testdouble.js instead of Sinon.js?

Testdouble.js has a simpler and more intuitive API than Sinon.js. It is designed to be easy to use and understand, so it is a great choice for those who prefer a more streamlined test method. It also encourages good testing practices by making it difficult to abuse test stand-ins.

Can I use Sinon.js and Testdouble.js with other test frameworks?

Yes, Sinon.js and Testdouble.js are both designed very neatly and work well with other test frameworks. They can be used with any JavaScript-enabled test framework.

What resources are there to learn more about Sinon.js and Testdouble.js?

Yes, Sinon.js and Testdouble.js have a lot of documentation on their official websites. There are also many tutorials, blog posts, and online courses covering in-depth content from these libraries.

The above is the detailed content of JavaScript Testing Tool Showdown: Sinon.js vs testdouble.js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn