No API Is the Best API — The elegant power of Power Assert

经过Evan Sangaline|2018年7月24日

One of the core ideas behind Facebook’sReactlibrary is that there should be no need to learn a new API for things that you already know how to do in vanilla JavaScript. Why bother memorizingAngular’sng-repeatsyntax when you can just use good-oldarray.map()? That’s a好主意那一种nd it’s a big part of what made the project so appealing to developers in the first place. So then why shouldJest–the same company’s JavaScript testing framework, and a popular choice among React developers–encourage you to learn a new assertion API, and to write code like

期待(结果).toequal(期望.Not.StringContaining(意外的有限公司));

when you一种lreadyknow how to assert the same thing using vanilla JavaScript?

一种ssert(!result.includes(unexpectedSubstring));

jest的API鼓励的那种断言模式似乎对反应背后的哲学来说是根本的。

This definitely isn’t a Jest-specific complaint (and Jest is also notably多得多而不是断言图书馆)。它适用于JavaScript中的大多数流行测试断言库,例如chaimust.js那一种ndshould.js。Testing in the JavaScript world tends to be heavily oriented towardsBDD.-style assertion libraries with verbose and complex assertion APIs in general. That’s fine if you’re into that kind of thing, but it’s a lot of extra syntax to memorize when you already know other ways to perform the same assertions in a simpler and more concise way.

Unsurprisingly, there are some good reasons for why these assertion libraries tend to work the way they do. If you simply use Node’s built in一种ssertmodule to make assertions, then your error messages provide you with fairly limited context about what’s being tested. The vanilla JavaScript version of our earlier “unexpected substring” test will fail with the same error message regardless of what the strings contain.

AssertionError [err_assertion]:false == true

That tells you that the test failed, but it would be useful to see what the values of the结果一种ndunexpectedSubstringvariables are so that you can determinewhythe test failed.

Here’s the output when the Jest test fails for comparison.

expect(received).toEqual(expected) Expected value to equal: StringNotContaining "World" Received: "Hello World"

This immediately lets us know that the string “Hello World” unexpectedly contained the substring “World.” We could of course write your own more informative assertion error messages using the vanilla approach, but this would requires additional work for each test. It’s not too bad for this substring case, something like this would suffice.

一种ssert( !result.includes(unexpectedSubstring), `"${result}" unexpectedly included "${unexpectedSubstring}".`, );

This approach really doesn’t scale well to more complicated tests however, especially when dealing with arrays and nested objects. With Jestet al.那you get useful and informative error messages out of the box.

For a long time, I thought that you had to choose between using a verbose assertion API or writing your own assertion messages. Well, it turns out that that isn’t the case! There’s a library out there calledPower Assertthat makes it possible to use the standard一种ssertmodule while still getting highly informative error messages. It’s the best of both worlds.

这B.一种sic premise behind Power Assert is that there’s no need to use a complex API in order to give an assertion library context about what’s being tested; that information can instead be deduced from the code itself. That of course means that Power Assert needs to have access tothe code itself那B.ut, for better or worse, JavaScript tends to make it’s way through a transformation or two before the code runs these days anyway. Using tools like禁止禁忌WebPack那一种ndGulpis the norm rather than the exception, and Power Assert has plugins available for each of these to provide more informative assertion messages.

Returning to our “unexpected substring” from earlier, Power Assert will transform the assertion so that it produces an error message like the following.

一种ssert(!result.includes(unexpectedSubstring)) || | | || true "World" |"Hello World" false + expected - actual -false +true

This message allows us to immediately see what the values of结果一种ndunexpectedSubstring评估期间,每个中间值。这code that was run here is exactly identical to what we ran earlier. The only thing that was changed was that I enabled the Babel Power Assert preset before running the test a second time. That’s literally all it takes to start getting more useful error messages if you’re already using Node’s assertion module. You don’t need to learn一种nynew assertion methods, you can just use the existing一种ssertAPI.

In fact, the slogan of Power Assert is “no API is the best API.” That’s a well-put sentiment that really resonates with me as a developer. It’s my favorite thing about React in comparison to earlier web frameworks, and it’s also one of the main ideas behind our browser automation framework远程浏览器。We developed the library to have as minimal of an API as possible while making it extremely easy for developers to use vanilla JavaScript,HTML browser contexts那一种nd theWeb扩展API.to accomplish complex testing and web scraping tasks (you can check out the远程浏览器互动之旅to see what I mean by that). We didn’t come up with as snazzy of a slogan, but I feel like it would apply nearly as well to Remote Browser as to Power Assert.

That resonance inspired me to reach out toTakuto Wada那the primary author of the Power Assert library. Long story short, we ended up doing a brief interview about the project. We’ll take a look at what he had to say in the next section, and then we’ll move on to a quick tutorial about how Power Assert can be integrated into a JavaScript project. Once it’s been added to an example project, we’ll construct a handful of tests as examples to see the sort of error messages that you can expect assertions to produce with Power Assert.

寻找一个令人兴奋的初创公司的工作?

We have some close personal friends building an awesome tutoring platform, and they're looking to hire. If this sounds like something you might be interested in, then拍摄我们一封电子邮件一种nd we'll get you in touch!

Mini-Interview with Takuto Wada

这se questions and answers are from a series of emails which Takuto Wada and I exchanged. Takuto Wada–in addition to being the primary author of Power Assert–has over 20 years of experience in programming, is well-known as a “TDD evangelist” in Japan, and has translated Kent Beck’sTest-Driven Developmentinto Japanese. Let’s see what he has to say about other testing frameworks, the origin of Power Assert, and the philosophy behind the project.

  1. 这re are some similar frameworks available in other languages which also use the “Power Assert” name, but it looks like your version came first based on their commit histories. Is that true? If so, did you coin the term “Power Assert,” and how did you initially get the idea for Power Assert?

    Not true. The origin of “Power Assert” isSpock framework写在groovy。当我第一次看到Groovy的力量时,我非常惊讶和吸引。

    On Jan 08, 2013, I’ve decided to implement power-assert as POC, using Mozilla SpiderMonkey Parser API, a predecessor of the ESTree Spec.

  2. Since you first started working on Power Assert, the popularity of major assertion libraries has shifted around a bit. One notable shift is that Jest has risen from being relatively obscure to being as popular as Chai. Do you have any thoughts on whether Jest provides a better assertion interface than Chai, or is it just more of the same?

    Just more of the same. Jest is a fork of Jasmine, kind of “BDD style” dialect of testing frameworks. Jest is more modest than chai, however it’s basically the same, tons of matchers, tons of APIs to learn.

    Kent Beck once said, “There is a clash between constraints. Easy to write vs. Easy to learn to write.” Chai, Jest, and its ancestor RSpec aims to “Easy to write”. Power-assert aims to “Easy to learn to write” = simple.

  3. Are there any utility libraries that you think couple particularly well with Power Assert? Informative error messages are half of the equation, but some assertions can require a bit of boilerplate to construct from scratch. Libraries like Lodash provide a wide range of methods that facilitate complex interactions with objects and arrays, and which can be used to write some tests in a less verbose way. Would you consider Lodash to be a good complement to Power Assert, or is it merely trading the complexity of one API for another?

    Functions or methods that simply returns boolean (e.g. lodash) works well with power-assert. Boolean functions tend to be avoided due to the less informative behavior on test failure, but with power-assert, you can gain informative failure message. Simplicity wins.

  4. 这philosophy behind Power Assert seems to be quite minimalist and in-line with Keep It Simple Stupid (KISS). I’m curious whether that’s a preference that carries over to your other development tooling. What does your development hardware/software setup look like, and do you consider it be oriented towards minimalist tools?

    我是20年的Unix Citizen,所以我的开发堆栈包括小而美丽的牙线工具。

    • Macbook Pro
    • Mainly Emacs, sometimes WebStorm, Atom, VSC.
    • zsh / nodebrew / ghq / peco

    As you guessed, philosophy behind my product is KISS principle. Simplicity matters most. My programming style is strongly influenced by Unix philosophy and also philosophy of Rich Hickey, the creator Clojure language.

  5. 您对权力的贡献是否纯粹是在您自己的时间内,或者您能够作为您工作的一部分工作吗?

    不幸的是,纯粹在我自己的时间。

  6. I’m going to go out on a limb here and guess that you believe that chainable semantic APIs in BDD assertion styles are needlessly complex.

    是的。

    如果是真的,那么你对链可以是可链式的语义apis感到相同的方式,还是有些情况在某些情况下它是一个不错的选择?

    I saw many Chainable semantic APIs in BDD assertion styles gradually become complex and complicated. They are “easy to write” but not “easy to learn to write”. Chainable semantic APIs (a.k.aFluent interface有时效果很好,特别是在编译器和IDE的方法完成帮助中以静态类型的语言(例如,在Java中编写的SQL查询生成器)。

    Is there a library that you know of that uses a chainable API that you think pulls it off really well?

    “Scopes”in Rails is well designed and works well.

  7. 我写这篇文章,因为我非常喜欢Power Assert, and I think that there are probably a lot of people who would love to use it but haven’t heard of it yet. What are a few open-source projects that you’re not affiliated with, but which you think more people should know about?

    I’d like to introduceghq一种ndpeco。这y made my OSS life really enjoyable.

  8. Is it OK if we steal the phrase “no API is the best API,” and use it when talking about Remote Browser :-)?

    Of course, yes :)

Setting Up Power Assert with Mocha and Babel

Let me start off by saying that there are很多插件,预设,任务以及使用各种JavaScript开发工具集的电源断言。我将在这里专注于使用Babel与Mocha因为这是我喜欢用于我自己的测试的设置。您可以查找主要与许多其他场景相关的指令Power Assert repositoryif you prefer a different testing setup. The actual error messages and testing strategies that we’ll look at should be independent of the tooling however, so it will hopefully be useful to see Power Assert in action regardless.

我还将提到最终的项目配置和我们写作的测试可用intoli-article-materialsrepository. If you’re trying to use this code as a template for adding Power Assert to your own project, then it may be useful to head over there where you can see the finished product. Oh, andstar the repository当你在那边!我们为那里的大多数文章施加了补充材料,主演了存储库是一个关于新的和即将到来的文章的好方法。

现在,这一切都脱离了,让我们开始在项目设置上。与JavaScript项目一样,您需要创建一个package.jsonfile in an otherwise clean working directory. Adding the following contents to thepackage.json文件将指定您在启用电源断言的运行测试所需的所有开发依赖项。

{ "scripts": { "test": "NODE_ENV=testing mocha --exit --require babel-register" }, "devDependencies": { "babel": "^6.23.0", "babel-core": "^6.26.3", "babel-preset-env": "^1.7.0", "babel-preset-power-assert": "^2.0.0", "babel-register": "^6.26.0", "mocha": "^5.2.0", "power-assert": "^1.5.0" } }

跑步npm installor纱线安装然后将依赖项安装到本地node_modules/subdirectory. Only two of these dependencies are Power Assert specific here… the ones with “power-assert” in their names. These are the mainpower-assert包装和套餐B.一种B.el-preset-power-assertBabel Propest,使力量主张与Babel一起使用。其他依赖性涵盖了Mocha测试框架和最小的Babel包装。

One other thing to note from thepackage.json文件是我们定义了一个testscript command. This isn’t specific to Power Assert, but we’re doing two things that are important here: setting theNODE_ENV环境变量到testing那一种nd using the- 要求B.一种B.el-register运行Mocha时的命令行参数。这- 要求标志只是告诉Mocha,它应该在运行测试之前需要特定的包。

在这种情况下,国旗告诉Mocha评估要求('Babel-Require')B.efore the tests. That seems fairly innocuous, but it has a profound impact on how our test code will be run. TheB.一种B.el-register包装是Babel的特殊部分,在必要时,它将自身绑定到节点require()方法并导致任何进一步要求的包裹在速度上被禁止侵入。

可以通过创建一个来配置rancedilation的行为.babelrc.file. A basic.babelrc.configuration which includes support for Power Assert might look something like the following.

{ "env": { "testing": { "presets": [ "power-assert" ] } }, "presets": [["env", { "targets": { "node": "6.10" } }]] }

这only part of this that’s actually specific to Power Assert is that we’ve told Babel to add the Power Assert preset whenNODE_ENVistestingIE。当我们运行我们的时候testscript). This testing configurationwill be mergedwith the default configuration. In this case, the default configuration just specifies that the禁止禁忌env preset应使用动态地确定目标节点6.10所需的插件。

After adding our.babelrc.file, everything should be ready to work with Babel and Power Assert. Now just create a subdirectory calledtest那the default search directory for Mocha, and create a JavaScript file calledtest/test-assertion-errors.jswith the following contents.

import assert from 'assert'; // Note that all of these tests are designed to fail, so that we can see the error messages! describe('Power Assert Testing Examples', () => { it('check that an unexpected substring is not found', () => { const result = 'Hello World'; const unexpectedSubstring = 'World'; // Jest Equivalent: expect(result).toEqual(expect.not.stringContaining(unexpectedSubstring)); assert(!result.includes(unexpectedSubstring)); }); });

We’re able to use theECMAScript 2015 import syntaxhere because our tests are being transpiled by Babel. Any other configuration that you add to your.babelrc.file will also apply to your tests, so you can use fun stuff like转换 - 对象休息一种ndtransform-optional-changingif you’re into that sort of thing. The only part that really matters for Power Assert, however, is that we specified the Power Assert preset as part of the configuration.

You can now run the test with eitheryarn testornpm run test那一种nd you’ll see the same informative error message that we looked at in the introduction.

一种ssert(!result.includes(unexpectedSubstring)) || | | || true "World" |"Hello World" false + expected - actual -false +true

您使用的任何其他测试一种ssertmodule will also include similarly helpful error messages. We’ll take a look at some practical examples in the next section!

Power Assert in Action

Now that we have everything configured, let’s take a quick look at a few common real-world testing patterns and the assertion messages that Power Assert produces in them. We’ll write each assertion using vanilla JavaScript and the Node一种ssertmodule, but we’ll also include commented-out Jest assertions for comparison. Note that only the individual tests definitions will be shown for brevity. Each test is intended to be placed into the描述()B.lock inside oftest/test-assertion-errors.js那so you can copy and paste them there if you would like to run the tests yourself (withyarn test).

Check that no members of an array are included in another array

In this first test, we’ll have an array called结果一种nd we’ll need to check that it doesn’t contain any of the elements from a second array calledunexpectedMembers。这re are several different logically equivalent ways that you could write an assertion for this test. You could loop through结果一种nd assert that each member isn’t part ofunexpectedMembers那you could loop throughunexpectedMembers一种nd assert that each member isn’t part of结果,你可以计算t之间的十字路口wo arrays considered as sets and assert that it’s the empty set,etc.I personally think that each of these approaches imply slightly different intentions, and that the most appropriate choice in any given situation depends on the underlying context and purpose of the test.

When I wrote this particular test, I was thinking of结果一种s a meaningful sequence of words andunexpectedMembers一种s a collection of unrelated words. Given that context, it makes sense to break the overall assertion up into a series of separate assertions where结果按顺序检查每个意外成员。我们可以用array.foreach()循环unexpectedMembers那一种nd thenarray.include()to check whether结果includes one of the unexpected members.

it('check that no members of an array are included in another array', () => { const result = ['Hello', 'World']; const unexpectedMembers = ['Evan', 'World']; // Jest Equivalent: expect(result).toEqual(expect.not.arrayContaining(unexpectedMembers)); unexpectedMembers.forEach(member => assert(!result.includes(member)) ); });

跑步this test will fail and produce the following error message.

一种ssert(!result.includes(member)) || | | || true "World" |["Hello","World"] false + expected - actual -false +true

This message shows us the full value of结果那the specific member ofunexpectedMembersthat the test is failing for, the fact that结果.Cludes(成员)evaluates totrue那一种nd that we’re explicitly negating that value to

Check that a regular expression matches a string

This one is pretty simple: we have a string called结果一种nd we want to check that a regular expression called正则表达式matches it. JavaScript has great support for regular expressions, and we can just useRegExp.test()to determine whether a regular expression matches the结果string.

it('check that a regular expression matches a string', () => { const regex = /^Hello World!/; const result = 'Hello World'; // Jest Equivalent: expect(result).toEqual(expect.stringMatching(regex)); assert(regex.test(result)); });

This test will fail due to the stray exclamation point in the regular expression, and the produced error message will look like this.

一种ssert(regex.test(result)) | | | | | "Hello World" | false /^Hello World!/ + expected - actual -false +true

We can easily see from this what the value of the regular expression is, the value of the结果string, and that the regular expression check failed.

Check that an array contains at least one number

In this example, we’ll want to check that an array called结果includes at least one number. TheArray.some()method allows us to check if a function evaluates totruefor any elements in an array. We can use this in conjunction with an arrow function that checks the type of each member in order to construct this test.

it('check that an array contains at least one number', () => { const result = ['Hello', 'World']; // Jest Equivalent: expect(result).toContainEqual(expect.any(Number)); assert(result.some(member => typeof member === 'number')); });

当然,此测试也会失败,其输出将看起来像这样。

断言(结果。组(成员=> Typeof成员==='编号'))|||假[“你好”,“世界”] +预期 - 实际--false +真实

This lets us clearly see how we defined the arrow function that checks each member, the value of the结果一种rray, and the fact that the arrow function evaluated to对于所有成员。

Check for deep equality between two objects

We’ve exclusively been making bare一种ssert()到目前为止拨打电话,但我应该指出一种ssert模块确实具有超出此延伸的API。这包括类似方法一种ssert.deepStrictEqual()/一种ssert.notDeepStrictEqual()for checking deep equality,一种ssert.throws()for checking that an error is thrown, and一种ssert.rejects()检查承诺是否被拒绝。当使用适用于第三方断言库的相同原因时,大部分API变得多余。例如,制造一个很少的好处assert.strictequal(a,b)call over一种ssert(a === b)with Power Assert, and similar statements apply to most of the rest of the API.

I consider the deep equality comparisons to be a notable exception to this. Although it’s quite possible to check this manually using recursion, this is one case where I don’t think it makes sense to code it yourself. The other examples that we’ve looked at so far really haven’t necessitated sacrificing any brevity of code or clarity of intention in order to use bare一种ssert()呼叫,但我们需要牺牲这两个人来手动检查平等。所以我们将使用一种ssert.deepStrictEqual()instead, and the assertion’s error message will still get transformed by Power Assert.

(检查深两个对象之间的平等,() => { const expectedResult = { 'a': [1, 2], 'b': [1, 2] } const result = { 'a': [1, 2], 'b': [1, 2, 3] } // Jest Equivalent: expect(result).toEqual(expectedResult); assert.deepStrictEqual(result, expectedResult); });

跑步this test will output the following.

一种ssert.deepStrictEqual(result, expectedResult) | | | Object{a:#Array#,b:#Array#} Object{a:#Array#,b:#Array#} + expected - actual ] "b": [ 1 2 - 3 ] }

这告诉我们都是结果一种nd预期结果一种re objects that both contain array properties called一种一种ndB.。We can also tell that the结果。B.一种rray includes a third element with a value of3.that isn’t expected to be there.

这里的错误消息相当有用,但值得注意的是,您可以自定义权力断言的行为,以显示每个数组的实际值而不是#大批#。这re’s amaxDepthoption which determines how many levels of nesting there can be before objects are replaced with generic identifiers. Customizing the behavior requires adding a bit of Power-Assert-specific code to our tests, but we can gracefully fall back to the standard一种ssertmodule when Power Assert isn’t being used with an initialization like this.

import uncustomizedAssert from 'assert'; const assert = !uncustomizedAssert.customize ? uncustomizedAssert : ( uncustomizedAssert.customize({ output: { maxDepth: 5, } }) );

This customization allows us to see the actual value of the arrays inside of each object.

一种ssert.deepEqual(result, expectedResult) | | | Object{a:[1,2],b:[1,2]} Object{a:[1,2],b:[1,2,3]} + expected - actual ] "b": [ 1 2 - 3 ] }

Conclusion

Well, I hope that this was able to inspire a few people to give Power Assert a try! The assertion messages that it produces won’t always be as specialized as those that you get from Jest or other libraries, but I’ve found them to be more than adequate for daily usage. It’s generally helpful to drop into a debugger anyway when you’re dealing with extremely complicated assertions, and the error messages become secondary at that point.

I used Mocha as the test runner for the examples here, but I would also like to give a quick shout-out toAVA。It’s a testing framework which uses both Babel and Power Assert out of the box, and it additionally includes some of Jest’s more interesting features (e.g.support forsnapshot testing). It’s highly opinionated, but it’s definitely worth checking out if there are things you like about both Jest and Power Assert.

Suggested Articles

If you enjoyed this article, then you might also enjoy these related ones.

Performing Efficient Broad Crawls with the AOPIC Algorithm

经过Andre Perunicic
on September 16, 2018

了解如何在广泛的爬网中估算页面重要性并分配带宽。

阅读更多

用户代理 - 使用Google Analytics和Circleci生成随机用户代理

经过Evan Sangaline
8月3.0, 2018

A free dataset and JavaScript library for generating random user agents that are always current.

阅读更多

使用ES6代理在JavaScript中重新创建Python的Slice语法

经过Evan Sangaline
on June 28, 2018

A gentle introduction to JavaScript proxies where we use them to recreate Python's extended slice syntax.

阅读更多

Comments