[{"data":1,"prerenderedAt":2230},["ShallowReactive",2],{"\u002Fposts\u002Fhow-to-write-unit-tests":3,"surround:\u002Fposts\u002Fhow-to-write-unit-tests":2221},{"id":4,"title":5,"body":6,"createdAt":2214,"description":170,"extension":2215,"meta":2216,"navigation":533,"path":2217,"seo":2218,"stem":2219,"__hash__":2220},"posts\u002Fposts\u002F2020\u002Fhow-to-write-unit-tests.md","如何编写单元测试",{"type":7,"value":8,"toc":2202},"minimark",[9,12,17,29,32,45,48,52,55,58,122,132,143,154,157,161,164,217,220,307,310,317,331,334,337,341,344,390,393,494,502,595,604,613,616,713,716,719,722,725,728,731,740,819,827,830,844,847,851,871,874,877,880,883,887,890,904,907,910,1038,1041,1106,1109,1112,1237,1240,1243,1464,1467,1470,1639,1642,1645,1758,1761,2015,2018,2195,2198],[10,11,5],"h1",{"id":5},[13,14,16],"h2",{"id":15},"what","什么是单元测试",[18,19,20,24,25,28],"p",{},[21,22,23],"strong",{},"单元测试","是什么？有些同学可能会觉得，在项目中可以通过命令运行的测试过程就是单元测试。如果这样理解的话，那你可能把单元测试和",[21,26,27],{},"自动化测试","搞混了。",[18,30,31],{},"单元测试是自动化测试的一种。一般来说（自动化）测试可以简单划分为单元测试、集成测试和功能测试。",[33,34,35,39,42],"ul",{},[36,37,38],"li",{},"单元测试：针对一个基本单元，一般是代码的一个模块，进行全方位的用例测试",[36,40,41],{},"集成测试：针对一整块逻辑进行测试",[36,43,44],{},"功能测试：针对整个系统进行测试",[18,46,47],{},"看起来，似乎只有单元测试能够实现自动化，但实际上并不是。比如我们也可以使用 Headless Chrome\u002FPuppeteer\u002FSelenium 进行自动化集成测试。",[13,49,51],{"id":50},"why","为啥要写单元测试",[18,53,54],{},"有个管理 Bug 的软件叫做 BugFree，这个名字除了表示它免费开源之外，bug-free 本身也被传播为一个形容词，表示一段程序是完全没有 Bug 的。当然这里仅只代码本身，否则硬杠的话你也可以说解释器\u002F浏览器\u002F操作系统\u002F电路出毛病也会产生 Bug。通常来说如果想要实现 bug-free，最好的方式就是列举出足够多的用例，并保证这些用例都能够符合预期地工作。这其实就是单元测试的概念。",[18,56,57],{},"当然，只针对单个模块的单元测试并不能保证完整功能的 bug-free，比如有个经典例子：",[59,60,61,119],"blockquote",{},[18,62,63,64,67,68,70,71,73,74,76,77,79,80,82,83,85,86,88,89,91,92,94,95,97,98,100,101,103,104,106,107,109,110,112,113,115,116,118],{},"一个测试工程师走进一家酒吧，要了一杯啤酒",[65,66],"br",{},"\n一个测试工程师走进一家酒吧，要了一杯咖啡",[65,69],{},"\n一个测试工程师走进一家酒吧，要了0.7杯啤酒",[65,72],{},"\n一个测试工程师走进一家酒吧，要了-1杯啤酒",[65,75],{},"\n一个测试工程师走进一家酒吧，要了2^32杯啤酒",[65,78],{},"\n一个测试工程师走进一家酒吧，要了一杯洗脚水",[65,81],{},"\n一个测试工程师走进一家酒吧，要了一杯蜥蜴",[65,84],{},"\n一个测试工程师走进一家酒吧，要了一份asdfQwer@24dg!&*(@",[65,87],{},"\n一个测试工程师走进一家酒吧，什么也没要",[65,90],{},"\n一个测试工程师走进一家酒吧，又走出去又从窗户进来又从后门出去从下水道钻进来",[65,93],{},"\n一个测试工程师走进一家酒吧，又走出去又进来又出去又进来又出去，最后在外面把老板打了一顿",[65,96],{},"\n一个测试工程师走进一",[65,99],{},"\n一个测试工程师走进一家酒吧，要了一杯烫烫烫的锟斤拷",[65,102],{},"\n一个测试工程师走进一家酒吧，要了NaN杯Null",[65,105],{},"\n1T测试工程师冲进一家酒吧，要了500T啤酒咖啡洗脚水野猫狼牙棒奶茶",[65,108],{},"\n1T测试工程师把酒吧拆了",[65,111],{},"\n一个测试工程师化装成老板走进一家酒吧，要了500杯啤酒并且不付钱",[65,114],{},"\n一万个测试工程师在酒吧门外呼啸而过",[65,117],{},"\n一个测试工程师走进一家酒吧，要了一杯啤酒';DROP TABLE 酒吧",[18,120,121],{},"测试工程师们满意地离开了酒吧。然后一名顾客点了一份炒饭，酒吧炸了",[18,123,124,125],{},"（引用来源：",[126,127,131],"a",{"href":128,"rel":129},"https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F20034686\u002Fanswer\u002F52063718%EF%BC%89",[130],"nofollow","https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F20034686\u002Fanswer\u002F52063718）",[18,133,134,135,138,139,142],{},"软件开发中有一种模式叫做",[21,136,137],{},"测试驱动开发","（Test-Driven Development, ",[21,140,141],{},"TDD","）。一般来说就是，在开发功能\u002F进行重构\u002F编写测试之前，我先把测试编写好，然后以能够实现这些测试用例为目标来编写\u002F修改我的代码。TDD 可以很大程度上保证代码的测试覆盖率，并且保证代码总是按照最初的预期进行工作。",[18,144,145,146,149,150,153],{},"但是 TDD 有一个问题是，我的测试用例要精确到什么程度。比如我要实现一个类，我需要测试它的私有属性吗？我需要测试它在不同情况下的不同副作用吗？如果过于精细的话，可能会导致“测试用例本身变成了代码”的情况，这样代码的正确性就变成了测试用例的正确性，测试本身就失去了意义。因此一般也会通过另一种叫做",[21,147,148],{},"行为驱动开发","（Behavior-Driven Development, ",[21,151,152],{},"BDD","）的模式来进行约束。",[18,155,156],{},"BDD 和 TDD 的概念基本上是一致的，但是 BDD 是面向结果而非过程的。也就是说，我不需要测试一段代码往数据库\u002FWeb Storage 里究竟写了什么值，我只需要知道它的返回值是正常的就可以。这一特点使得 BDD 编写测试的方式通常和 TDD 也有风格上的差异。",[13,158,160],{"id":159},"how","怎么写单元测试",[18,162,163],{},"其实说起来写单元测试很简单。比如你有一段代码：",[165,166,171],"pre",{"className":167,"code":168,"language":169,"meta":170,"style":170},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","function getText() {\n  return 'Hello, World!'\n}\n","js","",[172,173,174,194,211],"code",{"__ignoreMap":170},[175,176,179,183,187,191],"span",{"class":177,"line":178},"line",1,[175,180,182],{"class":181},"spNyl","function",[175,184,186],{"class":185},"s2Zo4"," getText",[175,188,190],{"class":189},"sMK4o","()",[175,192,193],{"class":189}," {\n",[175,195,197,201,204,208],{"class":177,"line":196},2,[175,198,200],{"class":199},"s7zQu","  return",[175,202,203],{"class":189}," '",[175,205,207],{"class":206},"sfazB","Hello, World!",[175,209,210],{"class":189},"'\n",[175,212,214],{"class":177,"line":213},3,[175,215,216],{"class":189},"}\n",[18,218,219],{},"如果想要验证它的输出，最简单的单元测试就是：",[165,221,223],{"className":167,"code":222,"language":169,"meta":170,"style":170},"\u002F\u002F unit-test.js\nfunction test() {\n  if (getText() !== 'Hello, World!') throw new Error('Unexpected output!')\n}\ntest()\n",[172,224,225,231,242,292,297],{"__ignoreMap":170},[175,226,227],{"class":177,"line":178},[175,228,230],{"class":229},"sHwdD","\u002F\u002F unit-test.js\n",[175,232,233,235,238,240],{"class":177,"line":196},[175,234,182],{"class":181},[175,236,237],{"class":185}," test",[175,239,190],{"class":189},[175,241,193],{"class":189},[175,243,244,247,251,254,257,260,262,264,267,270,273,276,279,282,284,287,289],{"class":177,"line":213},[175,245,246],{"class":199},"  if",[175,248,250],{"class":249},"swJcz"," (",[175,252,253],{"class":185},"getText",[175,255,256],{"class":249},"() ",[175,258,259],{"class":189},"!==",[175,261,203],{"class":189},[175,263,207],{"class":206},[175,265,266],{"class":189},"'",[175,268,269],{"class":249},") ",[175,271,272],{"class":199},"throw",[175,274,275],{"class":189}," new",[175,277,278],{"class":185}," Error",[175,280,281],{"class":249},"(",[175,283,266],{"class":189},[175,285,286],{"class":206},"Unexpected output!",[175,288,266],{"class":189},[175,290,291],{"class":249},")\n",[175,293,295],{"class":177,"line":294},4,[175,296,216],{"class":189},[175,298,300,303],{"class":177,"line":299},5,[175,301,302],{"class":185},"test",[175,304,306],{"class":305},"sTEyZ","()\n",[18,308,309],{},"每当我想要运行测试的时候，只需要运行 unit-test.js 查看是否有报错就可以了。但这可能会导致一些问题：当测试用例越来越多时，这样的管理方式可能不是很理想。",[18,311,312,313,316],{},"因此，我们通常需要一个",[21,314,315],{},"测试框架","。测试框架能提供给我们的不只是测试文件的管理，也包括：",[33,318,319,322,325,328],{},[36,320,321],{},"断言",[36,323,324],{},"并行执行",[36,326,327],{},"Mock",[36,329,330],{},"测试覆盖率",[18,332,333],{},"...等等。",[18,335,336],{},"因此在具体了解测试框架之前，可以先聊聊这些测试框架的功能。",[338,339,321],"h3",{"id":340},"assertion",[18,342,343],{},"写过其他语言的同学对这个概念应该不会陌生。在 C 语言里面就有 assert.h 这个头文件内置了断言的功能（一个小知识是，C 的 assert 实际上是一个宏而不是函数）。在其他语言里也有完全一致的功能，例如在浏览器中你也可以",[165,345,347],{"className":167,"code":346,"language":169,"meta":170,"style":170},"console.assert(1 + 1 === 0, 'Oops!')\n",[172,348,349],{"__ignoreMap":170},[175,350,351,354,357,360,362,366,369,372,375,378,381,383,386,388],{"class":177,"line":178},[175,352,353],{"class":305},"console",[175,355,356],{"class":189},".",[175,358,359],{"class":185},"assert",[175,361,281],{"class":305},[175,363,365],{"class":364},"sbssI","1",[175,367,368],{"class":189}," +",[175,370,371],{"class":364}," 1",[175,373,374],{"class":189}," ===",[175,376,377],{"class":364}," 0",[175,379,380],{"class":189},",",[175,382,203],{"class":189},[175,384,385],{"class":206},"Oops!",[175,387,266],{"class":189},[175,389,291],{"class":305},[18,391,392],{},"但这显然不够。比如某些情况可能我们需要：",[165,394,396],{"className":167,"code":395,"language":169,"meta":170,"style":170},"const value = foo()\nconsole.assert(\n  value && value.a === 1 && value.b === 2 && value.c === 3,\n  'foo() does not return { a: 1, b: 2, c: 3 }!',\n)\n",[172,397,398,414,425,478,490],{"__ignoreMap":170},[175,399,400,403,406,409,412],{"class":177,"line":178},[175,401,402],{"class":181},"const",[175,404,405],{"class":305}," value ",[175,407,408],{"class":189},"=",[175,410,411],{"class":185}," foo",[175,413,306],{"class":305},[175,415,416,418,420,422],{"class":177,"line":196},[175,417,353],{"class":305},[175,419,356],{"class":189},[175,421,359],{"class":185},[175,423,424],{"class":305},"(\n",[175,426,427,430,433,436,438,441,444,446,449,451,453,456,458,461,463,465,467,470,472,475],{"class":177,"line":213},[175,428,429],{"class":305},"  value ",[175,431,432],{"class":189},"&&",[175,434,435],{"class":305}," value",[175,437,356],{"class":189},[175,439,440],{"class":305},"a ",[175,442,443],{"class":189},"===",[175,445,371],{"class":364},[175,447,448],{"class":189}," &&",[175,450,435],{"class":305},[175,452,356],{"class":189},[175,454,455],{"class":305},"b ",[175,457,443],{"class":189},[175,459,460],{"class":364}," 2",[175,462,448],{"class":189},[175,464,435],{"class":305},[175,466,356],{"class":189},[175,468,469],{"class":305},"c ",[175,471,443],{"class":189},[175,473,474],{"class":364}," 3",[175,476,477],{"class":189},",\n",[175,479,480,483,486,488],{"class":177,"line":294},[175,481,482],{"class":189},"  '",[175,484,485],{"class":206},"foo() does not return { a: 1, b: 2, c: 3 }!",[175,487,266],{"class":189},[175,489,477],{"class":189},[175,491,492],{"class":177,"line":299},[175,493,291],{"class":305},[18,495,496,497,501],{},"在 Node.js 中，实际上有一个内置的模块 ",[126,498,359],{"href":499,"rel":500},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Fassert.html",[130],"，包含了一个断言的常见功能：deepEqual。例如",[165,503,505],{"className":167,"code":504,"language":169,"meta":170,"style":170},"const assert = require('assert')\n\nconst value = foo()\nassert.deepEqual(value, { a: 1, b: 2, c: 3 })\n",[172,506,507,529,535,547],{"__ignoreMap":170},[175,508,509,511,514,516,519,521,523,525,527],{"class":177,"line":178},[175,510,402],{"class":181},[175,512,513],{"class":305}," assert ",[175,515,408],{"class":189},[175,517,518],{"class":185}," require",[175,520,281],{"class":305},[175,522,266],{"class":189},[175,524,359],{"class":206},[175,526,266],{"class":189},[175,528,291],{"class":305},[175,530,531],{"class":177,"line":196},[175,532,534],{"emptyLinePlaceholder":533},true,"\n",[175,536,537,539,541,543,545],{"class":177,"line":213},[175,538,402],{"class":181},[175,540,405],{"class":305},[175,542,408],{"class":189},[175,544,411],{"class":185},[175,546,306],{"class":305},[175,548,549,551,553,556,559,561,564,567,570,572,574,577,579,581,583,586,588,590,593],{"class":177,"line":294},[175,550,359],{"class":305},[175,552,356],{"class":189},[175,554,555],{"class":185},"deepEqual",[175,557,558],{"class":305},"(value",[175,560,380],{"class":189},[175,562,563],{"class":189}," {",[175,565,566],{"class":249}," a",[175,568,569],{"class":189},":",[175,571,371],{"class":364},[175,573,380],{"class":189},[175,575,576],{"class":249}," b",[175,578,569],{"class":189},[175,580,460],{"class":364},[175,582,380],{"class":189},[175,584,585],{"class":249}," c",[175,587,569],{"class":189},[175,589,474],{"class":364},[175,591,592],{"class":189}," }",[175,594,291],{"class":305},[18,596,597,598,603],{},"此外，NPM 生态中也有很多例如 ",[126,599,602],{"href":600,"rel":601},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fpower-assert",[130],"power-assert"," 这样的库，在 assert 的基础上提供了更多实用的功能。",[18,605,606,607,612],{},"assert 风格通常被认为是 TDD 风格的，而在 BDD 中，由于 assert 并不能准确地表达“行为”这个概念，所以也有一些断言库为 Node 另一种风格的 API，最经典的就是 ",[126,608,611],{"href":609,"rel":610},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fchai",[130],"chai","。",[18,614,615],{},"Chai 同时支持 TDD\u002FBDD 风格的 API。所谓 TDD 风格也就是 assert 函数，而 BDD 风格则书写为：",[165,617,619],{"className":167,"code":618,"language":169,"meta":170,"style":170},"const { expect } = require('chai')\n\nconst value = foo()\nexpect(value).to.equal({ a: 1, b: 2, c: 3 })\n",[172,620,621,648,652,664],{"__ignoreMap":170},[175,622,623,625,627,630,633,636,638,640,642,644,646],{"class":177,"line":178},[175,624,402],{"class":181},[175,626,563],{"class":189},[175,628,629],{"class":305}," expect ",[175,631,632],{"class":189},"}",[175,634,635],{"class":189}," =",[175,637,518],{"class":185},[175,639,281],{"class":305},[175,641,266],{"class":189},[175,643,611],{"class":206},[175,645,266],{"class":189},[175,647,291],{"class":305},[175,649,650],{"class":177,"line":196},[175,651,534],{"emptyLinePlaceholder":533},[175,653,654,656,658,660,662],{"class":177,"line":213},[175,655,402],{"class":181},[175,657,405],{"class":305},[175,659,408],{"class":189},[175,661,411],{"class":185},[175,663,306],{"class":305},[175,665,666,669,672,674,677,679,682,684,687,689,691,693,695,697,699,701,703,705,707,709,711],{"class":177,"line":294},[175,667,668],{"class":185},"expect",[175,670,671],{"class":305},"(value)",[175,673,356],{"class":189},[175,675,676],{"class":305},"to",[175,678,356],{"class":189},[175,680,681],{"class":185},"equal",[175,683,281],{"class":305},[175,685,686],{"class":189},"{",[175,688,566],{"class":249},[175,690,569],{"class":189},[175,692,371],{"class":364},[175,694,380],{"class":189},[175,696,576],{"class":249},[175,698,569],{"class":189},[175,700,460],{"class":364},[175,702,380],{"class":189},[175,704,585],{"class":249},[175,706,569],{"class":189},[175,708,474],{"class":364},[175,710,592],{"class":189},[175,712,291],{"class":305},[18,714,715],{},"实际上两种风格能够实现的功能是一致的，但也可以看出，assert 是一种面向特性的风格，而 expect 则是一种面向结果的风格。",[338,717,324],{"id":718},"parallel",[18,720,721],{},"按照单元测试的习惯，每个测试之间都应该是互不干扰且没有副作用产生的。因此，测试之间应当是可以并行执行的。",[18,723,724],{},"常见的测试框架都支持并行执行。当然，如果一定不要使用框架的话，也可以通过 shell 命令来完成。不过“多条 shell 命令并行执行，但在任意一个触发错误后即停止所有命令”也是一个经典的问题。",[338,726,327],{"id":727},"mock",[18,729,730],{},"自动化的单元测试几乎一定需要 Mock 功能。试想你需要测试的功能依赖定时器函数，总不至于我需要等待多少时间执行定时器，测试代码就运行多少时间吧？再例如，我需要使用 Node.js 的 http 模块，但是我不希望在测试中直接发送请求。上面的情况都是需要对对应的模块进行 mock 的。",[18,732,733,734,739],{},"目前比较主流的 mock 库基本就是 ",[126,735,738],{"href":736,"rel":737},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fsinon",[130],"Sinon"," 了。首先，它提供了可以监听函数调用的功能：",[165,741,743],{"className":167,"code":742,"language":169,"meta":170,"style":170},"const spay = sinon.spy($, 'ajax')\n$.ajax(\u002F* ... *\u002F)\nexpect(spay.callCount).to.be(1)\n",[172,744,745,776,792],{"__ignoreMap":170},[175,746,747,749,752,754,757,759,762,765,767,769,772,774],{"class":177,"line":178},[175,748,402],{"class":181},[175,750,751],{"class":305}," spay ",[175,753,408],{"class":189},[175,755,756],{"class":305}," sinon",[175,758,356],{"class":189},[175,760,761],{"class":185},"spy",[175,763,764],{"class":305},"($",[175,766,380],{"class":189},[175,768,203],{"class":189},[175,770,771],{"class":206},"ajax",[175,773,266],{"class":189},[175,775,291],{"class":305},[175,777,778,781,783,785,787,790],{"class":177,"line":196},[175,779,780],{"class":305},"$",[175,782,356],{"class":189},[175,784,771],{"class":185},[175,786,281],{"class":305},[175,788,789],{"class":229},"\u002F* ... *\u002F",[175,791,291],{"class":305},[175,793,794,796,799,801,804,806,808,810,813,815,817],{"class":177,"line":213},[175,795,668],{"class":185},[175,797,798],{"class":305},"(spay",[175,800,356],{"class":189},[175,802,803],{"class":305},"callCount)",[175,805,356],{"class":189},[175,807,676],{"class":305},[175,809,356],{"class":189},[175,811,812],{"class":185},"be",[175,814,281],{"class":305},[175,816,365],{"class":364},[175,818,291],{"class":305},[18,820,821,822,612],{},"另外它也可以实现对于某个函数\u002F访问器实现的模拟。详细的接口可以看他们的",[126,823,826],{"href":824,"rel":825},"https:\u002F\u002Fsinonjs.org\u002F",[130],"官网",[338,828,330],{"id":829},"coverage",[18,831,832,833,838,839,612],{},"顾名思义，测试覆盖率指的是运行单元测试可以检查到的代码的占比。目前主流的覆盖率分析工具是 ",[126,834,837],{"href":835,"rel":836},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fistanbul",[130],"istanbul"," 及其继任者 ",[126,840,843],{"href":841,"rel":842},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fnyc",[130],"nyc",[18,845,846],{},"一般来说，覆盖率测试是通过 babel 这样的工具或是某些解释器提供的功能，来检查测试代码运行后执行过的代码行数、函数数量和分支数等等，最终给出一个近似的比例。",[338,848,850],{"id":849},"framework","框架",[18,852,853,854,859,860,859,865,870],{},"目前主流的测试框架有：",[126,855,858],{"href":856,"rel":857},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fjest",[130],"Jest","、",[126,861,864],{"href":862,"rel":863},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fmocha",[130],"Mocha",[126,866,869],{"href":867,"rel":868},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fava",[130],"Ava"," 等",[18,872,873],{},"Mocha 是一个相当老牌的测试框架，来自于非著名设计师 TJ Holowaychuk。上述功能它基本上都不支持，也正因如此，上述的主流工具库大多是为了填补 Mocha 的空白出现的。所以尽管 Mocha 支持的功能有限，但是 Mocha + Chai + Sinon + Nyc 就是一套测试利器了。",[18,875,876],{},"Jest 是 facebook 出品的一套大而全的测试框架，也是目前这些测试框架中下载量最高的，尽管主要原因是 create-react-app 内置了它。上述功能 Jest 都有内置，并且接口格式基本和主流库一致。",[18,878,879],{},"Ava 的卖点是轻量，但是之前一直都有一些稳定性问题。",[18,881,882],{},"总的来说，在工程化的项目中，使用 Jest 可能不是最好的选择，但基本是最简单的选择。",[13,884,886],{"id":885},"using-jest","使用 Jest 编写测试",[18,888,889],{},"了解了背景知识之后，就可以开始写了。下面的内容都假设我们使用了 Jest 作为测试框架。",[18,891,892,893,896,897,900,901,903],{},"一般来说，我们项目中会约定一些专门用于存放单元测试文件的目录（比如 ",[172,894,895],{},"__tests__","，",[172,898,899],{},"__specs__","，或者直接就是 tests 或 specs），或者是通过后缀名（.test.js 或者 .spec.js）来区分。在 Jest 里面，默认支持 ",[172,902,895],{}," 和两种后缀名。如果觉得下划线文件夹比较丑的话，后缀名其实是个很好的选择，这和 .d.ts 或者 .stories.js 也比较一致。尽管如此，把测试放在单独的目录也是比较容易管理的，因为你可能会在某些工具库中对测试代码单独进行配置，比方说 ESLint 之类的。",[18,905,906],{},"对于 Jest 来说，一些常见的方法，比如 describe、test\u002Fit、expect 还有 jest 对象都是全局变量，所以不需要使用 import 或者 require。直接新建一个 .test.js，然后开始写就完了。",[18,908,909],{},"假设我们有一个 foo.js",[165,911,913],{"className":167,"code":912,"language":169,"meta":170,"style":170},"export function getFoo(value) {\n  if (typeof value === 'string') {\n    try {\n      value = JSON.parse(value)\n    } catch {\n      return undefined\n    }\n  }\n  return value.foo\n}\n",[172,914,915,937,962,969,990,1000,1009,1015,1021,1033],{"__ignoreMap":170},[175,916,917,920,923,926,928,932,935],{"class":177,"line":178},[175,918,919],{"class":199},"export",[175,921,922],{"class":181}," function",[175,924,925],{"class":185}," getFoo",[175,927,281],{"class":189},[175,929,931],{"class":930},"sHdIc","value",[175,933,934],{"class":189},")",[175,936,193],{"class":189},[175,938,939,941,943,946,948,950,952,955,957,959],{"class":177,"line":196},[175,940,246],{"class":199},[175,942,250],{"class":249},[175,944,945],{"class":189},"typeof",[175,947,435],{"class":305},[175,949,374],{"class":189},[175,951,203],{"class":189},[175,953,954],{"class":206},"string",[175,956,266],{"class":189},[175,958,269],{"class":249},[175,960,961],{"class":189},"{\n",[175,963,964,967],{"class":177,"line":213},[175,965,966],{"class":199},"    try",[175,968,193],{"class":189},[175,970,971,974,976,979,981,984,986,988],{"class":177,"line":294},[175,972,973],{"class":305},"      value",[175,975,635],{"class":189},[175,977,978],{"class":305}," JSON",[175,980,356],{"class":189},[175,982,983],{"class":185},"parse",[175,985,281],{"class":249},[175,987,931],{"class":305},[175,989,291],{"class":249},[175,991,992,995,998],{"class":177,"line":299},[175,993,994],{"class":189},"    }",[175,996,997],{"class":199}," catch",[175,999,193],{"class":189},[175,1001,1003,1006],{"class":177,"line":1002},6,[175,1004,1005],{"class":199},"      return",[175,1007,1008],{"class":189}," undefined\n",[175,1010,1012],{"class":177,"line":1011},7,[175,1013,1014],{"class":189},"    }\n",[175,1016,1018],{"class":177,"line":1017},8,[175,1019,1020],{"class":189},"  }\n",[175,1022,1024,1026,1028,1030],{"class":177,"line":1023},9,[175,1025,200],{"class":199},[175,1027,435],{"class":305},[175,1029,356],{"class":189},[175,1031,1032],{"class":305},"foo\n",[175,1034,1036],{"class":177,"line":1035},10,[175,1037,216],{"class":189},[18,1039,1040],{},"OK，我们可以开始写测试了：",[165,1042,1044],{"className":167,"code":1043,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\ndescribe('getFoo', () => {\n  \u002F\u002F ...\n})\n",[172,1045,1046,1067,1071,1095,1100],{"__ignoreMap":170},[175,1047,1048,1051,1053,1055,1057,1060,1062,1065],{"class":177,"line":178},[175,1049,1050],{"class":199},"import",[175,1052,563],{"class":189},[175,1054,925],{"class":305},[175,1056,592],{"class":189},[175,1058,1059],{"class":199}," from",[175,1061,203],{"class":189},[175,1063,1064],{"class":206},".\u002Ffoo",[175,1066,210],{"class":189},[175,1068,1069],{"class":177,"line":196},[175,1070,534],{"emptyLinePlaceholder":533},[175,1072,1073,1076,1078,1080,1083,1085,1087,1090,1093],{"class":177,"line":213},[175,1074,1075],{"class":185},"describe",[175,1077,281],{"class":305},[175,1079,266],{"class":189},[175,1081,1082],{"class":206},"getFoo",[175,1084,266],{"class":189},[175,1086,380],{"class":189},[175,1088,1089],{"class":189}," ()",[175,1091,1092],{"class":181}," =>",[175,1094,193],{"class":189},[175,1096,1097],{"class":177,"line":294},[175,1098,1099],{"class":229},"  \u002F\u002F ...\n",[175,1101,1102,1104],{"class":177,"line":299},[175,1103,632],{"class":189},[175,1105,291],{"class":305},[18,1107,1108],{},"describe 实际上是在描述你在测试哪个模块，它可以帮助我们在一个文件里面进行多个类别的测试。比方说我们可能一般习惯对于每一个 .js 都建一个 .test.js，然后每一个导出都写一个 describe。",[18,1110,1111],{},"describe 的回调函数里则可以编写测试代码：",[165,1113,1115],{"className":167,"code":1114,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\ndescribe('getFoo', () => {\n\n  it('should return the value of `foo` when receiving an object', () => {\n    expect(getFoo({ foo: 1 })).toBe(1)\n  })\n\n})\n",[172,1116,1117,1135,1139,1159,1163,1185,1220,1227,1231],{"__ignoreMap":170},[175,1118,1119,1121,1123,1125,1127,1129,1131,1133],{"class":177,"line":178},[175,1120,1050],{"class":199},[175,1122,563],{"class":189},[175,1124,925],{"class":305},[175,1126,592],{"class":189},[175,1128,1059],{"class":199},[175,1130,203],{"class":189},[175,1132,1064],{"class":206},[175,1134,210],{"class":189},[175,1136,1137],{"class":177,"line":196},[175,1138,534],{"emptyLinePlaceholder":533},[175,1140,1141,1143,1145,1147,1149,1151,1153,1155,1157],{"class":177,"line":213},[175,1142,1075],{"class":185},[175,1144,281],{"class":305},[175,1146,266],{"class":189},[175,1148,1082],{"class":206},[175,1150,266],{"class":189},[175,1152,380],{"class":189},[175,1154,1089],{"class":189},[175,1156,1092],{"class":181},[175,1158,193],{"class":189},[175,1160,1161],{"class":177,"line":294},[175,1162,534],{"emptyLinePlaceholder":533},[175,1164,1165,1168,1170,1172,1175,1177,1179,1181,1183],{"class":177,"line":299},[175,1166,1167],{"class":185},"  it",[175,1169,281],{"class":249},[175,1171,266],{"class":189},[175,1173,1174],{"class":206},"should return the value of `foo` when receiving an object",[175,1176,266],{"class":189},[175,1178,380],{"class":189},[175,1180,1089],{"class":189},[175,1182,1092],{"class":181},[175,1184,193],{"class":189},[175,1186,1187,1190,1192,1194,1196,1198,1200,1202,1204,1206,1209,1211,1214,1216,1218],{"class":177,"line":1002},[175,1188,1189],{"class":185},"    expect",[175,1191,281],{"class":249},[175,1193,1082],{"class":185},[175,1195,281],{"class":249},[175,1197,686],{"class":189},[175,1199,411],{"class":249},[175,1201,569],{"class":189},[175,1203,371],{"class":364},[175,1205,592],{"class":189},[175,1207,1208],{"class":249},"))",[175,1210,356],{"class":189},[175,1212,1213],{"class":185},"toBe",[175,1215,281],{"class":249},[175,1217,365],{"class":364},[175,1219,291],{"class":249},[175,1221,1222,1225],{"class":177,"line":1011},[175,1223,1224],{"class":189},"  }",[175,1226,291],{"class":249},[175,1228,1229],{"class":177,"line":1017},[175,1230,534],{"emptyLinePlaceholder":533},[175,1232,1233,1235],{"class":177,"line":1023},[175,1234,632],{"class":189},[175,1236,291],{"class":305},[18,1238,1239],{},"这是一个最简单的测试用例。这个 it 函数用来声明接下来的测试内容测试的是什么样的用例，很多时候也写作 test，不过 it 可能更具有语义性。内容则很简单，是一段 BDD 风格的测试代码。总的来说，这个例子还是比较像是一段英语的。",[18,1241,1242],{},"有时候你也可以做更加详细的测试，例如",[165,1244,1246],{"className":167,"code":1245,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\ndescribe('getFoo', () => {\n\n  it('should visit the value of `foo` when receiving an object', () => {\n    const object = { foo: 1 }\n    const spy = jest.spyOn(object, 'foo', 'get')\n    const result = getFoo({ foo: 1 })\n    expect(result).toBe(1)\n    expect(spy).toHaveBeenCalledTimes(1)\n  })\n\n})\n",[172,1247,1248,1266,1270,1290,1294,1315,1336,1378,1403,1424,1445,1452,1457],{"__ignoreMap":170},[175,1249,1250,1252,1254,1256,1258,1260,1262,1264],{"class":177,"line":178},[175,1251,1050],{"class":199},[175,1253,563],{"class":189},[175,1255,925],{"class":305},[175,1257,592],{"class":189},[175,1259,1059],{"class":199},[175,1261,203],{"class":189},[175,1263,1064],{"class":206},[175,1265,210],{"class":189},[175,1267,1268],{"class":177,"line":196},[175,1269,534],{"emptyLinePlaceholder":533},[175,1271,1272,1274,1276,1278,1280,1282,1284,1286,1288],{"class":177,"line":213},[175,1273,1075],{"class":185},[175,1275,281],{"class":305},[175,1277,266],{"class":189},[175,1279,1082],{"class":206},[175,1281,266],{"class":189},[175,1283,380],{"class":189},[175,1285,1089],{"class":189},[175,1287,1092],{"class":181},[175,1289,193],{"class":189},[175,1291,1292],{"class":177,"line":294},[175,1293,534],{"emptyLinePlaceholder":533},[175,1295,1296,1298,1300,1302,1305,1307,1309,1311,1313],{"class":177,"line":299},[175,1297,1167],{"class":185},[175,1299,281],{"class":249},[175,1301,266],{"class":189},[175,1303,1304],{"class":206},"should visit the value of `foo` when receiving an object",[175,1306,266],{"class":189},[175,1308,380],{"class":189},[175,1310,1089],{"class":189},[175,1312,1092],{"class":181},[175,1314,193],{"class":189},[175,1316,1317,1320,1323,1325,1327,1329,1331,1333],{"class":177,"line":1002},[175,1318,1319],{"class":181},"    const",[175,1321,1322],{"class":305}," object",[175,1324,635],{"class":189},[175,1326,563],{"class":189},[175,1328,411],{"class":249},[175,1330,569],{"class":189},[175,1332,371],{"class":364},[175,1334,1335],{"class":189}," }\n",[175,1337,1338,1340,1343,1345,1348,1350,1353,1355,1358,1360,1362,1365,1367,1369,1371,1374,1376],{"class":177,"line":1011},[175,1339,1319],{"class":181},[175,1341,1342],{"class":305}," spy",[175,1344,635],{"class":189},[175,1346,1347],{"class":305}," jest",[175,1349,356],{"class":189},[175,1351,1352],{"class":185},"spyOn",[175,1354,281],{"class":249},[175,1356,1357],{"class":305},"object",[175,1359,380],{"class":189},[175,1361,203],{"class":189},[175,1363,1364],{"class":206},"foo",[175,1366,266],{"class":189},[175,1368,380],{"class":189},[175,1370,203],{"class":189},[175,1372,1373],{"class":206},"get",[175,1375,266],{"class":189},[175,1377,291],{"class":249},[175,1379,1380,1382,1385,1387,1389,1391,1393,1395,1397,1399,1401],{"class":177,"line":1017},[175,1381,1319],{"class":181},[175,1383,1384],{"class":305}," result",[175,1386,635],{"class":189},[175,1388,925],{"class":185},[175,1390,281],{"class":249},[175,1392,686],{"class":189},[175,1394,411],{"class":249},[175,1396,569],{"class":189},[175,1398,371],{"class":364},[175,1400,592],{"class":189},[175,1402,291],{"class":249},[175,1404,1405,1407,1409,1412,1414,1416,1418,1420,1422],{"class":177,"line":1023},[175,1406,1189],{"class":185},[175,1408,281],{"class":249},[175,1410,1411],{"class":305},"result",[175,1413,934],{"class":249},[175,1415,356],{"class":189},[175,1417,1213],{"class":185},[175,1419,281],{"class":249},[175,1421,365],{"class":364},[175,1423,291],{"class":249},[175,1425,1426,1428,1430,1432,1434,1436,1439,1441,1443],{"class":177,"line":1035},[175,1427,1189],{"class":185},[175,1429,281],{"class":249},[175,1431,761],{"class":305},[175,1433,934],{"class":249},[175,1435,356],{"class":189},[175,1437,1438],{"class":185},"toHaveBeenCalledTimes",[175,1440,281],{"class":249},[175,1442,365],{"class":364},[175,1444,291],{"class":249},[175,1446,1448,1450],{"class":177,"line":1447},11,[175,1449,1224],{"class":189},[175,1451,291],{"class":249},[175,1453,1455],{"class":177,"line":1454},12,[175,1456,534],{"emptyLinePlaceholder":533},[175,1458,1460,1462],{"class":177,"line":1459},13,[175,1461,632],{"class":189},[175,1463,291],{"class":305},[18,1465,1466],{},"这个例子除了返回值之外，还测试了对对象的 foo 属性是否访问过，且是否只访问了一次。但是，这样详细的测试实际上是对实现的耦合，很大程度上和 BDD 的思想是相悖的。通常我们只需要验证结果就可以了，如果希望也可以验证属性是否被访问，但是验证访问次数就显得多余了。",[18,1468,1469],{},"有时候你可能有多个用例，希望复用一些 mock 代码，则可以：",[165,1471,1473],{"className":167,"code":1472,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\nbeforeEach(() => {\n  localStorage.setItem('foo', 'bar')\n})\n\ndescribe('getFoo', () => {\n\n  it('should return the value of `foo` when receiving an built-in object', () => {\n    expect(getFoo(localStorage)).toBe('bar')\n  })\n\n})\n",[172,1474,1475,1493,1497,1510,1539,1545,1549,1569,1573,1594,1623,1629,1633],{"__ignoreMap":170},[175,1476,1477,1479,1481,1483,1485,1487,1489,1491],{"class":177,"line":178},[175,1478,1050],{"class":199},[175,1480,563],{"class":189},[175,1482,925],{"class":305},[175,1484,592],{"class":189},[175,1486,1059],{"class":199},[175,1488,203],{"class":189},[175,1490,1064],{"class":206},[175,1492,210],{"class":189},[175,1494,1495],{"class":177,"line":196},[175,1496,534],{"emptyLinePlaceholder":533},[175,1498,1499,1502,1504,1506,1508],{"class":177,"line":213},[175,1500,1501],{"class":185},"beforeEach",[175,1503,281],{"class":305},[175,1505,190],{"class":189},[175,1507,1092],{"class":181},[175,1509,193],{"class":189},[175,1511,1512,1515,1517,1520,1522,1524,1526,1528,1530,1532,1535,1537],{"class":177,"line":294},[175,1513,1514],{"class":305},"  localStorage",[175,1516,356],{"class":189},[175,1518,1519],{"class":185},"setItem",[175,1521,281],{"class":249},[175,1523,266],{"class":189},[175,1525,1364],{"class":206},[175,1527,266],{"class":189},[175,1529,380],{"class":189},[175,1531,203],{"class":189},[175,1533,1534],{"class":206},"bar",[175,1536,266],{"class":189},[175,1538,291],{"class":249},[175,1540,1541,1543],{"class":177,"line":299},[175,1542,632],{"class":189},[175,1544,291],{"class":305},[175,1546,1547],{"class":177,"line":1002},[175,1548,534],{"emptyLinePlaceholder":533},[175,1550,1551,1553,1555,1557,1559,1561,1563,1565,1567],{"class":177,"line":1011},[175,1552,1075],{"class":185},[175,1554,281],{"class":305},[175,1556,266],{"class":189},[175,1558,1082],{"class":206},[175,1560,266],{"class":189},[175,1562,380],{"class":189},[175,1564,1089],{"class":189},[175,1566,1092],{"class":181},[175,1568,193],{"class":189},[175,1570,1571],{"class":177,"line":1017},[175,1572,534],{"emptyLinePlaceholder":533},[175,1574,1575,1577,1579,1581,1584,1586,1588,1590,1592],{"class":177,"line":1023},[175,1576,1167],{"class":185},[175,1578,281],{"class":249},[175,1580,266],{"class":189},[175,1582,1583],{"class":206},"should return the value of `foo` when receiving an built-in object",[175,1585,266],{"class":189},[175,1587,380],{"class":189},[175,1589,1089],{"class":189},[175,1591,1092],{"class":181},[175,1593,193],{"class":189},[175,1595,1596,1598,1600,1602,1604,1607,1609,1611,1613,1615,1617,1619,1621],{"class":177,"line":1035},[175,1597,1189],{"class":185},[175,1599,281],{"class":249},[175,1601,1082],{"class":185},[175,1603,281],{"class":249},[175,1605,1606],{"class":305},"localStorage",[175,1608,1208],{"class":249},[175,1610,356],{"class":189},[175,1612,1213],{"class":185},[175,1614,281],{"class":249},[175,1616,266],{"class":189},[175,1618,1534],{"class":206},[175,1620,266],{"class":189},[175,1622,291],{"class":249},[175,1624,1625,1627],{"class":177,"line":1447},[175,1626,1224],{"class":189},[175,1628,291],{"class":249},[175,1630,1631],{"class":177,"line":1454},[175,1632,534],{"emptyLinePlaceholder":533},[175,1634,1635,1637],{"class":177,"line":1459},[175,1636,632],{"class":189},[175,1638,291],{"class":305},[18,1640,1641],{},"beforeEach 会在每个测试运行前执行。如果希望只执行一次，可以用 beforeAll。",[18,1643,1644],{},"上面的这个例子有一个明显的问题，就是没有清理掉额外写入的 localStorage，这会对其他的测试产生影响，所以应该要：",[165,1646,1648],{"className":167,"code":1647,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\nbeforeEach(() => {\n  localStorage.setItem('foo', 'bar')\n})\n\nafterEach(() => {\n  localStorage.removeItem('foo')\n})\n",[172,1649,1650,1668,1672,1684,1710,1716,1720,1733,1752],{"__ignoreMap":170},[175,1651,1652,1654,1656,1658,1660,1662,1664,1666],{"class":177,"line":178},[175,1653,1050],{"class":199},[175,1655,563],{"class":189},[175,1657,925],{"class":305},[175,1659,592],{"class":189},[175,1661,1059],{"class":199},[175,1663,203],{"class":189},[175,1665,1064],{"class":206},[175,1667,210],{"class":189},[175,1669,1670],{"class":177,"line":196},[175,1671,534],{"emptyLinePlaceholder":533},[175,1673,1674,1676,1678,1680,1682],{"class":177,"line":213},[175,1675,1501],{"class":185},[175,1677,281],{"class":305},[175,1679,190],{"class":189},[175,1681,1092],{"class":181},[175,1683,193],{"class":189},[175,1685,1686,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708],{"class":177,"line":294},[175,1687,1514],{"class":305},[175,1689,356],{"class":189},[175,1691,1519],{"class":185},[175,1693,281],{"class":249},[175,1695,266],{"class":189},[175,1697,1364],{"class":206},[175,1699,266],{"class":189},[175,1701,380],{"class":189},[175,1703,203],{"class":189},[175,1705,1534],{"class":206},[175,1707,266],{"class":189},[175,1709,291],{"class":249},[175,1711,1712,1714],{"class":177,"line":299},[175,1713,632],{"class":189},[175,1715,291],{"class":305},[175,1717,1718],{"class":177,"line":1002},[175,1719,534],{"emptyLinePlaceholder":533},[175,1721,1722,1725,1727,1729,1731],{"class":177,"line":1011},[175,1723,1724],{"class":185},"afterEach",[175,1726,281],{"class":305},[175,1728,190],{"class":189},[175,1730,1092],{"class":181},[175,1732,193],{"class":189},[175,1734,1735,1737,1739,1742,1744,1746,1748,1750],{"class":177,"line":1017},[175,1736,1514],{"class":305},[175,1738,356],{"class":189},[175,1740,1741],{"class":185},"removeItem",[175,1743,281],{"class":249},[175,1745,266],{"class":189},[175,1747,1364],{"class":206},[175,1749,266],{"class":189},[175,1751,291],{"class":249},[175,1753,1754,1756],{"class":177,"line":1023},[175,1755,632],{"class":189},[175,1757,291],{"class":305},[18,1759,1760],{},"实际上，你也可以不访问 localStorage 来避免实际写入值：",[165,1762,1764],{"className":167,"code":1763,"language":169,"meta":170,"style":170},"import { getFoo } from '.\u002Ffoo'\n\nbeforeEach(() => {\n  localStorage.setItem('foo', 'bar')\n})\n\ndescribe('getFoo', () => {\n\n  it('should return the value of `foo` when receiving an built-in object', () => {\n    const spy = jest.spyOn(localStorage, 'foo', 'get')\n      .mockImplemention(() => 'bar')\n    expect(getFoo(localStorage)).toBe('bar')\n    expect(spy).toHaveBeenCalled()\n    spy.mockRestore()\n  })\n\n})\n",[172,1765,1766,1784,1788,1800,1826,1832,1836,1856,1860,1880,1916,1938,1966,1983,1996,2003,2008],{"__ignoreMap":170},[175,1767,1768,1770,1772,1774,1776,1778,1780,1782],{"class":177,"line":178},[175,1769,1050],{"class":199},[175,1771,563],{"class":189},[175,1773,925],{"class":305},[175,1775,592],{"class":189},[175,1777,1059],{"class":199},[175,1779,203],{"class":189},[175,1781,1064],{"class":206},[175,1783,210],{"class":189},[175,1785,1786],{"class":177,"line":196},[175,1787,534],{"emptyLinePlaceholder":533},[175,1789,1790,1792,1794,1796,1798],{"class":177,"line":213},[175,1791,1501],{"class":185},[175,1793,281],{"class":305},[175,1795,190],{"class":189},[175,1797,1092],{"class":181},[175,1799,193],{"class":189},[175,1801,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824],{"class":177,"line":294},[175,1803,1514],{"class":305},[175,1805,356],{"class":189},[175,1807,1519],{"class":185},[175,1809,281],{"class":249},[175,1811,266],{"class":189},[175,1813,1364],{"class":206},[175,1815,266],{"class":189},[175,1817,380],{"class":189},[175,1819,203],{"class":189},[175,1821,1534],{"class":206},[175,1823,266],{"class":189},[175,1825,291],{"class":249},[175,1827,1828,1830],{"class":177,"line":299},[175,1829,632],{"class":189},[175,1831,291],{"class":305},[175,1833,1834],{"class":177,"line":1002},[175,1835,534],{"emptyLinePlaceholder":533},[175,1837,1838,1840,1842,1844,1846,1848,1850,1852,1854],{"class":177,"line":1011},[175,1839,1075],{"class":185},[175,1841,281],{"class":305},[175,1843,266],{"class":189},[175,1845,1082],{"class":206},[175,1847,266],{"class":189},[175,1849,380],{"class":189},[175,1851,1089],{"class":189},[175,1853,1092],{"class":181},[175,1855,193],{"class":189},[175,1857,1858],{"class":177,"line":1017},[175,1859,534],{"emptyLinePlaceholder":533},[175,1861,1862,1864,1866,1868,1870,1872,1874,1876,1878],{"class":177,"line":1023},[175,1863,1167],{"class":185},[175,1865,281],{"class":249},[175,1867,266],{"class":189},[175,1869,1583],{"class":206},[175,1871,266],{"class":189},[175,1873,380],{"class":189},[175,1875,1089],{"class":189},[175,1877,1092],{"class":181},[175,1879,193],{"class":189},[175,1881,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914],{"class":177,"line":1035},[175,1883,1319],{"class":181},[175,1885,1342],{"class":305},[175,1887,635],{"class":189},[175,1889,1347],{"class":305},[175,1891,356],{"class":189},[175,1893,1352],{"class":185},[175,1895,281],{"class":249},[175,1897,1606],{"class":305},[175,1899,380],{"class":189},[175,1901,203],{"class":189},[175,1903,1364],{"class":206},[175,1905,266],{"class":189},[175,1907,380],{"class":189},[175,1909,203],{"class":189},[175,1911,1373],{"class":206},[175,1913,266],{"class":189},[175,1915,291],{"class":249},[175,1917,1918,1921,1924,1926,1928,1930,1932,1934,1936],{"class":177,"line":1447},[175,1919,1920],{"class":189},"      .",[175,1922,1923],{"class":185},"mockImplemention",[175,1925,281],{"class":249},[175,1927,190],{"class":189},[175,1929,1092],{"class":181},[175,1931,203],{"class":189},[175,1933,1534],{"class":206},[175,1935,266],{"class":189},[175,1937,291],{"class":249},[175,1939,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964],{"class":177,"line":1454},[175,1941,1189],{"class":185},[175,1943,281],{"class":249},[175,1945,1082],{"class":185},[175,1947,281],{"class":249},[175,1949,1606],{"class":305},[175,1951,1208],{"class":249},[175,1953,356],{"class":189},[175,1955,1213],{"class":185},[175,1957,281],{"class":249},[175,1959,266],{"class":189},[175,1961,1534],{"class":206},[175,1963,266],{"class":189},[175,1965,291],{"class":249},[175,1967,1968,1970,1972,1974,1976,1978,1981],{"class":177,"line":1459},[175,1969,1189],{"class":185},[175,1971,281],{"class":249},[175,1973,761],{"class":305},[175,1975,934],{"class":249},[175,1977,356],{"class":189},[175,1979,1980],{"class":185},"toHaveBeenCalled",[175,1982,306],{"class":249},[175,1984,1986,1989,1991,1994],{"class":177,"line":1985},14,[175,1987,1988],{"class":305},"    spy",[175,1990,356],{"class":189},[175,1992,1993],{"class":185},"mockRestore",[175,1995,306],{"class":249},[175,1997,1999,2001],{"class":177,"line":1998},15,[175,2000,1224],{"class":189},[175,2002,291],{"class":249},[175,2004,2006],{"class":177,"line":2005},16,[175,2007,534],{"emptyLinePlaceholder":533},[175,2009,2011,2013],{"class":177,"line":2010},17,[175,2012,632],{"class":189},[175,2014,291],{"class":305},[18,2016,2017],{},"另外有些时候，你可能还需要测试一些异步的过程，Jest 对于异步的支持也是比较好的：",[165,2019,2021],{"className":167,"code":2020,"language":169,"meta":170,"style":170},"import { getAsyncFoo } from '..\u002Ffoo'\n\ndescribe('getAsyncFoo', () => {\n\n  it('should work well', async () => {\n    const result = getAsyncFoo()\n    expect(result).toBeInstanceOf(Promise)\n    await expect(result).resolves.toBe('foo')\n    \u002F\u002F 或者也可以\n    \u002F\u002F const value = await result\n    \u002F\u002F expect(value).toBe('foo')\n  })\n\n})\n",[172,2022,2023,2043,2047,2068,2072,2096,2108,2131,2164,2169,2174,2179,2185,2189],{"__ignoreMap":170},[175,2024,2025,2027,2029,2032,2034,2036,2038,2041],{"class":177,"line":178},[175,2026,1050],{"class":199},[175,2028,563],{"class":189},[175,2030,2031],{"class":305}," getAsyncFoo",[175,2033,592],{"class":189},[175,2035,1059],{"class":199},[175,2037,203],{"class":189},[175,2039,2040],{"class":206},"..\u002Ffoo",[175,2042,210],{"class":189},[175,2044,2045],{"class":177,"line":196},[175,2046,534],{"emptyLinePlaceholder":533},[175,2048,2049,2051,2053,2055,2058,2060,2062,2064,2066],{"class":177,"line":213},[175,2050,1075],{"class":185},[175,2052,281],{"class":305},[175,2054,266],{"class":189},[175,2056,2057],{"class":206},"getAsyncFoo",[175,2059,266],{"class":189},[175,2061,380],{"class":189},[175,2063,1089],{"class":189},[175,2065,1092],{"class":181},[175,2067,193],{"class":189},[175,2069,2070],{"class":177,"line":294},[175,2071,534],{"emptyLinePlaceholder":533},[175,2073,2074,2076,2078,2080,2083,2085,2087,2090,2092,2094],{"class":177,"line":299},[175,2075,1167],{"class":185},[175,2077,281],{"class":249},[175,2079,266],{"class":189},[175,2081,2082],{"class":206},"should work well",[175,2084,266],{"class":189},[175,2086,380],{"class":189},[175,2088,2089],{"class":181}," async",[175,2091,1089],{"class":189},[175,2093,1092],{"class":181},[175,2095,193],{"class":189},[175,2097,2098,2100,2102,2104,2106],{"class":177,"line":1002},[175,2099,1319],{"class":181},[175,2101,1384],{"class":305},[175,2103,635],{"class":189},[175,2105,2031],{"class":185},[175,2107,306],{"class":249},[175,2109,2110,2112,2114,2116,2118,2120,2123,2125,2129],{"class":177,"line":1011},[175,2111,1189],{"class":185},[175,2113,281],{"class":249},[175,2115,1411],{"class":305},[175,2117,934],{"class":249},[175,2119,356],{"class":189},[175,2121,2122],{"class":185},"toBeInstanceOf",[175,2124,281],{"class":249},[175,2126,2128],{"class":2127},"sBMFI","Promise",[175,2130,291],{"class":249},[175,2132,2133,2136,2139,2141,2143,2145,2147,2150,2152,2154,2156,2158,2160,2162],{"class":177,"line":1017},[175,2134,2135],{"class":199},"    await",[175,2137,2138],{"class":185}," expect",[175,2140,281],{"class":249},[175,2142,1411],{"class":305},[175,2144,934],{"class":249},[175,2146,356],{"class":189},[175,2148,2149],{"class":305},"resolves",[175,2151,356],{"class":189},[175,2153,1213],{"class":185},[175,2155,281],{"class":249},[175,2157,266],{"class":189},[175,2159,1364],{"class":206},[175,2161,266],{"class":189},[175,2163,291],{"class":249},[175,2165,2166],{"class":177,"line":1023},[175,2167,2168],{"class":229},"    \u002F\u002F 或者也可以\n",[175,2170,2171],{"class":177,"line":1035},[175,2172,2173],{"class":229},"    \u002F\u002F const value = await result\n",[175,2175,2176],{"class":177,"line":1447},[175,2177,2178],{"class":229},"    \u002F\u002F expect(value).toBe('foo')\n",[175,2180,2181,2183],{"class":177,"line":1454},[175,2182,1224],{"class":189},[175,2184,291],{"class":249},[175,2186,2187],{"class":177,"line":1459},[175,2188,534],{"emptyLinePlaceholder":533},[175,2190,2191,2193],{"class":177,"line":1985},[175,2192,632],{"class":189},[175,2194,291],{"class":305},[18,2196,2197],{},"编写更多的测试用例有助于你发现一些逻辑问题。比如上面的 getFoo，在测试了足够的用例之后就能发现，当传入 null 时会发生错误。针对这样的情况，就可以修改代码来进行修复，从而进一步接近 bug-free。",[2199,2200,2201],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":170,"searchDepth":196,"depth":196,"links":2203},[2204,2205,2206,2213],{"id":15,"depth":196,"text":16},{"id":50,"depth":196,"text":51},{"id":159,"depth":196,"text":160,"children":2207},[2208,2209,2210,2211,2212],{"id":340,"depth":213,"text":321},{"id":718,"depth":213,"text":324},{"id":727,"depth":213,"text":327},{"id":829,"depth":213,"text":330},{"id":849,"depth":213,"text":850},{"id":885,"depth":196,"text":886},"2020-12-20","md",{},"\u002Fposts\u002Fhow-to-write-unit-tests",{"title":5,"description":170},"posts\u002F2020\u002Fhow-to-write-unit-tests","ryOBe_RUc0ybnmSuoeGnz3IM9UnKgjmu0uGGtk-y-iw",[2222,2226],{"title":2223,"path":2224,"stem":2225,"children":-1},"TypeScript 开发技巧","\u002Fposts\u002Ftypescript-development-tips","posts\u002F2020\u002Ftypescript-development-tips",{"title":2227,"path":2228,"stem":2229,"children":-1},"JavaScript 黑历史","\u002Fposts\u002Fthe-charming-javascript","posts\u002F2022\u002Fthe-charming-javascript",1792500630764]