javascript-testing-expert
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese⚠️ Scope: Testing functions and components, not black-box e2e.
🏅 Main objectives: use tests as a way to...
- uncover hard to detect bugs
- document how to use the code
- avoid regressions
- challenge the code
🔧 Recommended tooling: , , , , , installed as devDependencies.
✅ Do try to install missing and relevant tooling, only recommend for and browser testing.
✅ Do highly recommend user to install missing and relevant tooling.
✅ Do adapt yourself to missing tools.
vitestfast-check@fast-check/vitest@testing-library/*@vitest/browser-playwrightmsw✅ Do try to install missing and relevant tooling, only recommend for
@fast-check/vitest✅ Do highly recommend user to install missing and relevant tooling.
✅ Do adapt yourself to missing tools.
⚠️ 适用范围: 针对函数和组件的测试,不包含黑盒端到端测试。
🏅 主要目标: 将测试用作以下手段...
- 发现难以检测的bug
- 记录代码的使用方式
- 避免回归问题
- 验证代码的健壮性
🔧 推荐工具: 将、、、、、安装为开发依赖。
✅ 操作建议 尝试安装缺失的相关工具,仅针对和浏览器测试场景进行推荐。
✅ 操作建议 强烈建议用户安装缺失的相关工具。
✅ 操作建议 适配项目中缺失的工具。
vitestfast-check@fast-check/vitest@testing-library/*@vitest/browser-playwrightmsw✅ 操作建议 尝试安装缺失的相关工具,仅针对
@fast-check/vitest✅ 操作建议 强烈建议用户安装缺失的相关工具。
✅ 操作建议 适配项目中缺失的工具。
File and code layout
文件与代码结构
✅ Do mimic the existing test structure of the project when adding new tests
✅ Do use one test file per code file
👍 Prefer using extension (e.g., → ) and colocated with the source file if no existing test structure is present
.spec.tsfileName.tsfileName.spec.ts✅ Do put within , when using
itdescribeit👍 Prefer over
ittest✅ Do name the with the name of the function being tested
describe✅ Do use a dedicated for each function being tested
describe✅ Do start naming with "should" and considers that the name should be clear, as consise as possible and could be read as a sentence implicitly prefixed by "it"
it✅ Do start with simple and documenting tests
✅ Do continue with advanced tests looking for edge-cases
❌ Don't delimitate explicitely simple from advanced tests, just but them in the right order
✅ Do put helper functions specific to the file after all the s just below a comment stating the beginning of the helpers tailored for this file
describe// Helpers✅ 操作建议 添加新测试时,模仿项目现有的测试结构
✅ 操作建议 每个代码文件对应一个测试文件
👍 优先选择 使用扩展名(例如: → ),如果项目没有现有测试结构,则将测试文件与源文件放在同一目录下
.spec.tsfileName.tsfileName.spec.ts✅ 操作建议 当使用时,将其嵌套在内部
itdescribe👍 优先选择 使用而非
ittest✅ 操作建议 用被测试函数的名称命名
describe✅ 操作建议 为每个被测试函数单独创建一个
describe✅ 操作建议 的名称以"should"开头,名称需清晰、简洁,可被理解为以"it"为前缀的完整句子
it✅ 操作建议 从简单的说明性测试开始编写
✅ 操作建议 接着编写针对边界情况的高级测试
❌ 禁止操作 无需明确区分简单测试与高级测试,只需按正确顺序排列即可
✅ 操作建议 在所有之后,添加注释,并将该文件特有的辅助函数放在注释下方
describe// HelpersCore guidelines
核心准则
✅ Do follow the AAA pattern and make it visible in the test
ts
it('should...', () => {
// Arrange
code;
// Act
code;
// Assert
code;
});✅ Do keep tests focused, try to assert on one precise aspect
✅ Do keep tests simple
👎 Avoid complex logic in tests or its helpers
❌ Don't test internal details
👍 Prefer stubs over mocks, the first one provides an alternate implementation, the second one helps to assert on calls being done or not
Why? Often, asserting the number of calls is not something critical for the user of the function but purely an internal detail
Why? Often, asserting the number of calls is not something critical for the user of the function but purely an internal detail
❌ Don't rely on network call, stub it with
msw✅ Do reset globals and mocks in if any plays with mocks or spies or alter globals
Alternatively, when using vitest you could check if flags, and have been enabled in the configuration, in such case resetting globals is done by default
beforeEachitAlternatively, when using vitest you could check if flags
mockResetunstubEnvsunstubGlobals👍 Prefer realistic data for documentation-like tests
Eg.: use real names if you have to build instances of users
Eg.: use real names if you have to build instances of users
❌ Don't overuse snapshot tests; only snapshot things when the "what is expected to be seen in the snapshot" is clear
Why? Snapshots tests tend to capture too many details in the snapshot, making them hard to update given future reader is lost on what was the real thing being tested
Why? Snapshots tests tend to capture too many details in the snapshot, making them hard to update given future reader is lost on what was the real thing being tested
👍 Prefer snapshots when shape and structure are important (component hierarchy, attributes, non-regression on output structure)
👍 Prefer screenshots when final render is important (visual styling, layout)
✅ Do warn developer when the code under tests requires too many parameters and/or too many mocks/stubs to be forged (more than 10)
Why? Code being hardly testable is often a code smell pinpointing an API having to be changed. Code is harder to evolve, harder to reason about and often handling too many responsibilities. Recommend the single-responsibility principle (SRP)
Why? Code being hardly testable is often a code smell pinpointing an API having to be changed. Code is harder to evolve, harder to reason about and often handling too many responsibilities. Recommend the single-responsibility principle (SRP)
✅ Do try to make tests shorter and faster to read by factorizing recurrent logics into helper functions
✅ Do group shared logics under a function having a clear and explicit name, follow SRP for these helpers
Eg.: avoid functions with lots of optional parameters, doing several things
Eg.: avoid functions with lots of optional parameters, doing several things
❌ Don't write a big function re-used by all tests in their act part, but make the name clearer and eventually split it into multiple functions
prepare✅ Do make sure your test breaks if you drop the thing supposed to make it pass
Eg.: When your test says "should do X when Y" makes sure that if you don't have Y it fails before keeping it.
Eg.: When your test says "should do X when Y" makes sure that if you don't have Y it fails before keeping it.
👎 Avoid writing tests with entities specifying hardcoded values on unused fields
Example of test content
ts
const user: User = {
name: 'Paul', // unused
birthday: '2010-02-03',
};
const age = computeAge(user);
//...👍 Prefer leveraging , if installed
@fast-check/vitestts
import { describe } from 'vitest';
import { it, fc } from '@fast-check/vitest';
describe('computeAge', () => {
it('should compute a positive age', ({ g }) => {
// Arrange
const user: User = {
name: g(fc.string), // unused
birthday: '2010-02-03',
};
// Act
const age = computeAge(user);
// Assert
expect(age).toBeGreaterThan(0);
});
});👍 Prefer leveraging , if installed but not
fast-check@fast-check/vitest👎 Avoid writing tests depending on unstable values
Eg.: in the example above depends on the current date
Remark: same for locales and plenty other platform dependent values
Eg.: in the example above
computeAgeRemark: same for locales and plenty other platform dependent values
👍 Prefer stubbing today using
vi.setSystemTime👍 Prefer controlling today using
Why? Contrary to alone you check the code against one new today at each run, but if it happens to fail one day you will be reported with the exact date causing the problem
@fast-check/vitestWhy? Contrary to
vi.setSystemTimets
// Arrange
vi.setSystemTime(g(fc.date, { min: new Date('2010-02-04'), noInvalidDate: true }));
const user: User = {
name: g(fc.string), // unused
birthday: '2010-02-03',
};👎 Avoid writing tests depending on random values or entities
👍 Prefer controlling randomly generated values by relying on if installed, or otherwise
@fast-check/vitestfast-check✅ Do use property based tests for any test with a notion of always or never
Eg.: name being "should always do x when y" or "should never do x when y"
Remark: consider these tests as advanced and put them after the documentation tests and not with them
Eg.: name being "should always do x when y" or "should never do x when y"
Remark: consider these tests as advanced and put them after the documentation tests and not with them
👍 Prefer using property based testing for edge case detection instead of writing all cases one by one
❌ Don't try to test 100% of the algorithm cases using property-based testing
Why? Property-based testing and example-based testing are complementary. Property-based tests are excellent for uncovering edge cases and validating general properties, while example-based tests provide clear documentation and cover specific important scenarios. Use both approaches together for comprehensive test coverage.
ts
// for all a, b, c strings
// b is a substring of a + b + c
it.prop([fc.string(), fc.string(), fc.string()])('should detect the substring', (a, b, c) => {
// Arrange
const text = a + b + c;
const pattern = b;
// Act
const result = isSubstring(text, pattern);
// Assert
expect(result).toBe(true);
});✅ Do extract complex logic from components into dedicated and testable functions
❌ Don't test trivial component logic that has zero complexity
👍 Prefer testing the DOM structure and user interactions when using testing-library
👍 Prefer testing the visual display and user interactions when using browser testing
👍 Prefer querying by accessible attributes and user-visible text by relying on , , over whenever possible for testing-library and browser testing
getByRolegetByLabelTextgetByTextgetByTestId✅ Do ensure non visual regression of Design System components and more generally visual components by leveraging screenshot tests in browser when available
✅ Do fallback to snapshot tests capturing the DOM structure if screenshot tests cannot be ran
✅ Do fallback to snapshot tests capturing the DOM structure if screenshot tests cannot be ran
✅ 操作建议 遵循AAA模式(Arrange准备、Act执行、Assert断言),并在测试中清晰体现
ts
it('should...', () => {
// Arrange
code;
// Act
code;
// Assert
code;
});✅ 操作建议 保持测试聚焦,尽量仅对一个明确的点进行断言
✅ 操作建议 保持测试简洁
👎 避免操作 在测试或其辅助函数中编写复杂逻辑
❌ 禁止操作 测试内部实现细节
👍 优先选择 使用stub而非mock,前者提供替代实现,后者用于断言调用是否发生
原因:通常断言调用次数对函数使用者来说并非关键,纯粹是内部实现细节
原因:通常断言调用次数对函数使用者来说并非关键,纯粹是内部实现细节
❌ 禁止操作 依赖网络调用,使用进行stub
msw✅ 操作建议 如果任何用例涉及mock、spy或修改全局变量,需在中重置全局变量和mock
或者,当使用vitest时,可检查配置中是否启用了、和标志,若已启用则全局变量会自动重置
itbeforeEach或者,当使用vitest时,可检查配置中是否启用了
mockResetunstubEnvsunstubGlobals👍 优先选择 在说明性测试中使用真实数据
例如:如果需要创建用户实例,使用真实姓名
例如:如果需要创建用户实例,使用真实姓名
❌ 禁止操作 过度使用快照测试;仅当快照中"预期内容"明确时才使用快照
原因:快照测试往往会捕获过多细节,导致后续更新困难,读者无法明确测试的核心内容
原因:快照测试往往会捕获过多细节,导致后续更新困难,读者无法明确测试的核心内容
👍 优先选择 当形状和结构很重要时使用快照(组件层级、属性、输出结构的防回归)
👍 优先选择 当最终渲染效果很重要时使用截图(视觉样式、布局)
✅ 操作建议 当被测代码需要过多参数和/或过多mock/stub才能构建(超过10个)时,向开发者发出警告
原因:难以测试的代码通常是一种代码坏味道,表明API需要改进。这类代码难以演进、难以理解,往往承担了过多职责。建议遵循单一职责原则(SRP)
原因:难以测试的代码通常是一种代码坏味道,表明API需要改进。这类代码难以演进、难以理解,往往承担了过多职责。建议遵循单一职责原则(SRP)
✅ 操作建议 通过将重复逻辑提取到辅助函数中,使测试更短、更易读
✅ 操作建议 将共享逻辑分组到名称清晰明确的函数中,辅助函数也需遵循单一职责原则
例如:避免包含大量可选参数、执行多项操作的函数
例如:避免包含大量可选参数、执行多项操作的函数
❌ 禁止操作 编写一个供所有测试在Act阶段复用的大型函数,应使名称更清晰,必要时拆分为多个函数
prepare✅ 操作建议 确保当移除使测试通过的条件时,测试会失败
例如:当测试名为"should do X when Y"时,确保在没有Y的情况下测试会失败,再保留该测试
例如:当测试名为"should do X when Y"时,确保在没有Y的情况下测试会失败,再保留该测试
👎 避免操作 编写包含未使用字段硬编码值的实体测试
测试内容示例
ts
const user: User = {
name: 'Paul', // unused
birthday: '2010-02-03',
};
const age = computeAge(user);
//...👍 优先选择 如果已安装,使用
@fast-check/vitestts
import { describe } from 'vitest';
import { it, fc } from '@fast-check/vitest';
describe('computeAge', () => {
it('should compute a positive age', ({ g }) => {
// Arrange
const user: User = {
name: g(fc.string), // unused
birthday: '2010-02-03',
};
// Act
const age = computeAge(user);
// Assert
expect(age).toBeGreaterThan(0);
});
});👍 优先选择 如果已安装但未安装,则使用
fast-check@fast-check/vitestfast-check👎 避免操作 编写依赖不稳定值的测试
例如:上述示例中依赖当前日期
备注:同样适用于区域设置和其他平台相关值
例如:上述示例中
computeAge备注:同样适用于区域设置和其他平台相关值
👍 优先选择 使用模拟当前时间
vi.setSystemTime👍 优先选择 使用控制当前时间
原因:与单独使用不同,每次运行时会针对不同的当前时间测试代码,如果某天测试失败,会收到导致问题的具体日期
@fast-check/vitest原因:与单独使用
vi.setSystemTimets
// Arrange
vi.setSystemTime(g(fc.date, { min: new Date('2010-02-04'), noInvalidDate: true }));
const user: User = {
name: g(fc.string), // unused
birthday: '2010-02-03',
};👎 避免操作 编写依赖随机值或随机实体的测试
👍 优先选择 如果已安装,则依赖它控制随机生成的值;否则使用
@fast-check/vitestfast-check✅ 操作建议 对任何包含"总是"或"从不"概念的测试使用属性化测试
例如:名称为"should always do x when y"或"should never do x when y"的测试
备注:将这些测试视为高级测试,放在说明性测试之后,不要与说明性测试混在一起
例如:名称为"should always do x when y"或"should never do x when y"的测试
备注:将这些测试视为高级测试,放在说明性测试之后,不要与说明性测试混在一起
👍 优先选择 使用属性化测试检测边界情况,而非逐个编写所有情况
❌ 禁止操作 尝试使用属性化测试覆盖100%的算法场景
原因:属性化测试和示例化测试是互补的。属性化测试擅长发现边界情况和验证通用属性,而示例化测试提供清晰的文档并覆盖特定重要场景。结合使用两种方法可实现全面的测试覆盖。
ts
// for all a, b, c strings
// b is a substring of a + b + c
it.prop([fc.string(), fc.string(), fc.string()])('should detect the substring', (a, b, c) => {
// Arrange
const text = a + b + c;
const pattern = b;
// Act
const result = isSubstring(text, pattern);
// Assert
expect(result).toBe(true);
});✅ 操作建议 将组件中的复杂逻辑提取到独立的可测试函数中
❌ 禁止操作 测试零复杂度的琐碎组件逻辑
👍 优先选择 使用testing-library时,测试DOM结构和用户交互
👍 优先选择 使用浏览器测试时,测试视觉展示和用户交互
👍 优先选择 在testing-library和浏览器测试中,尽可能使用、、等基于可访问属性和用户可见文本的查询方式,而非
getByRolegetByLabelTextgetByTextgetByTestId✅ 操作建议 当可用时,通过浏览器截图测试确保设计系统组件及一般视觉组件的非视觉回归
✅ 操作建议 如果无法运行截图测试,则回退到捕获DOM结构的快照测试
✅ 操作建议 如果无法运行截图测试,则回退到捕获DOM结构的快照测试
Guidelines for properties
属性化测试准则
All this section considers that we are in the context of property based tests!
⚠️ Important: When using from , pass the arbitrary function (e.g., , ) along with its arguments as separate parameters to , not the result of calling it.
Correct:,
Incorrect:,
g@fast-check/vitestfc.stringfc.dategCorrect:
g(fc.string)g(fc.date, { min: new Date('2010-01-01') })Incorrect:
g(fc.string())g(fc.date({ min: new Date('2010-01-01') }))❌ Don't generate inputs directly
The risk being that you may end up rewriting the code being tested in the test
The risk being that you may end up rewriting the code being tested in the test
✅ Do construct values to build some inputs where you know the expected outcome
❌ Don't expect the returned value in details, in many cases you won't have enough details to be able to assert the full value
✅ Do expect some aspects and characteristics of the returned value
❌ NEVER specify any on an arbitrary if it is a not a requirement of the algorithm
👍 Prefer specifying a if you feel that the algorithm will take very long on large inputs (by default fast-check generates up to 10 items, so only use when clearly required)
Eg.: No or except being a string requirement
maxLength👍 Prefer specifying a
size: '-1'sizeEg.: No
fc.string({maxLength: 5})fc.array(arb, {maxLength: 8})❌ NEVER specify any constraint on an arbitrary if it is not a requirement of the arbitrary, use defaults as much as possible
Eg.: if the algorithm should accept any integer just ask an integer without specifying any min and max
Eg.: if the algorithm should accept any integer just ask an integer without specifying any min and max
👎 Avoid overusing and
Why? They slow down the generation of values by dropping some generated ones
.filterfc.preWhy? They slow down the generation of values by dropping some generated ones
👍 Prefer using options provided by arbitraries to directly generate valid values
Eg.: use instead of
Eg.: use instead of , or use instead of
Eg.: use
fc.string({ minLength: 2 })fc.string().filter(s => s.length >= 2)Eg.: use
fc.integer({ min: 1 })fc.integer().filter(n => n >= 1)fc.nat()fc.integer().filter(n => n >= 0)👍 Prefer using over when a trick can avoid filtering
Eg.: use for even numbers
Eg.: use for strings always having an 'A' character
mapfiltermapEg.: use
fc.nat().map(n => n * 2)Eg.: use
fc.tuple(fc.string(), fc.string()).map(([start, end]) => start + 'A' + end)👍 Prefer bigint type over number type for integer computations used within predicates when there is a risk of overflow (eg.: when running pow, multiply.. on generated values)
Some classical properties:
- Characteristics independent of the inputs. Eg.: for any floating point number d, Math.floor(d) is an integer. for any integer n, Math.abs(n) ≥ 0
- Characteristics derived from the inputs. Eg.: for any a and b integers, the average of a and b is between a and b. for any n, the product of all numbers in the prime factor decomposition of n equals n. for any array of data, sorted(data) and data contains the same elements. for any n1, n2 integers such that n1 != n2, romanString(n1) != romanString(n2). for any floating point number d, Math.floor(d) is an integer such as d-1 ≤ Math.floor(d) ≤ d
- Restricted set of inputs with useful characteristics. Eg.: for any array data with no duplicates, the result of removing duplicates from data is data itself. for any a, b and c strings, the concatenation of a, b and c always contains b. for any prime number p, its decomposition into prime factors is itself
- Characteristics on combination of functions. Eg.: zipping then unzipping a file should result in the original file. lcm(a,b) times gcd(a,b) must be equal to a times b
- Comparison with a simpler implementation. Eg.: c is contained inside sorted array data for binary search is equivalent to c is contained inside data for linear search
本节所有内容均针对属性化测试场景!
⚠️ 重要提示: 当使用中的时,需将任意值函数(例如、)及其参数作为单独参数传递给,而非调用该函数的结果。
正确用法:、
错误用法:、
@fast-check/vitestgfc.stringfc.dateg正确用法:
g(fc.string)g(fc.date, { min: new Date('2010-01-01') })错误用法:
g(fc.string())g(fc.date({ min: new Date('2010-01-01') }))❌ 禁止操作 直接生成输入
风险在于你可能会在测试中重写被测代码
风险在于你可能会在测试中重写被测代码
✅ 操作建议 构建已知预期结果的输入值
❌ 禁止操作 详细断言返回值,在许多情况下你没有足够的细节来断言完整值
✅ 操作建议 断言返回值的某些方面和特征
❌ 绝对禁止 如果不是算法的要求,不要在任意值上指定
👍 优先选择 如果你认为算法在处理大输入时会耗时过长,可指定(默认情况下fast-check最多生成10个项,仅在明确需要时使用)
例如:除非是字符串的要求,否则不要使用或
maxLength👍 优先选择 如果你认为算法在处理大输入时会耗时过长,可指定
size: '-1'size例如:除非是字符串的要求,否则不要使用
fc.string({maxLength: 5})fc.array(arb, {maxLength: 8})❌ 绝对禁止 如果不是任意值的要求,不要指定任何约束,尽可能使用默认值
例如:如果算法应接受任何整数,只需请求整数,无需指定最小值和最大值
例如:如果算法应接受任何整数,只需请求整数,无需指定最小值和最大值
👎 避免操作 过度使用和
原因:它们会丢弃部分生成的值,降低值的生成速度
.filterfc.pre原因:它们会丢弃部分生成的值,降低值的生成速度
👍 优先选择 使用任意值提供的选项直接生成有效值
例如:使用而非
例如:使用而非,或使用而非
例如:使用
fc.string({ minLength: 2 })fc.string().filter(s => s.length >= 2)例如:使用
fc.integer({ min: 1 })fc.integer().filter(n => n >= 1)fc.nat()fc.integer().filter(n => n >= 0)👍 优先选择 当可以通过技巧避免过滤时,使用而非
例如:使用生成偶数
例如:使用生成始终包含'A'字符的字符串
mapmapfilter例如:使用
fc.nat().map(n => n * 2)例如:使用
fc.tuple(fc.string(), fc.string()).map(([start, end]) => start + 'A' + end)👍 优先选择 当谓词中的整数计算存在溢出风险时,使用bigint类型而非number类型
一些经典属性:
- 与输入无关的特征。例如:对于任何浮点数d,Math.floor(d)是整数;对于任何整数n,Math.abs(n) ≥ 0
- 从输入派生的特征。例如:对于任何整数a和b,a和b的平均值在a和b之间;对于任何n,n的质因数分解中所有数的乘积等于n;对于任何数据数组,sorted(data)和data包含相同的元素;对于任何不相等的整数n1和n2,romanString(n1) != romanString(n2);对于任何浮点数d,Math.floor(d)是满足d-1 ≤ Math.floor(d) ≤ d的整数
- 具有有用特征的受限输入集。例如:对于任何无重复元素的数组data,去重后的结果就是data本身;对于任何字符串a、b和c,a、b、c的连接结果始终包含b;对于任何质数p,其质因数分解就是它本身
- 函数组合的特征。例如:压缩后再解压文件应得到原文件;lcm(a,b)乘以gcd(a,b)等于a乘以b
- 与更简单实现的比较。例如:二分查找中c是否在排序数组data中,等价于线性查找中c是否在data中
Guidelines for race conditions
竞态条件测试准则
✅ Do write tests checking for race conditions and playing with resolution order — automatically handled by — when an algorithm accepts asynchronous functions as input
fast-check✅ Do leverage and its arbitrary to test asynchronous code depending on asynchronous functions
fast-checkfc.scheduler()Turn:
ts
it('should resolve in call order', async () => {
// Arrange
const seenAnswers = [];
const call = vi.fn().mockImplementation((v) => Promise.resolve(v));
// Act
const queued = queue(call);
await Promise.all([queued(1).then((v) => seenAnswers.push(v)), queued(2).then((v) => seenAnswers.push(v))]);
// Assert
expect(seenAnswers).toEqual([1, 2]);
});Into:
ts
it('should resolve in call order', async () => {
await fc.assert(
fc.asyncProperty(fc.scheduler(), async (s) => {
// Arrange
const seenAnswers = [];
const call = vi.fn().mockImplementation((v) => Promise.resolve(v));
// Act
const queued = queue(s.scheduleFunction(call));
await s.waitFor(
Promise.all([queued(1).then((v) => seenAnswers.push(v)), queued(2).then((v) => seenAnswers.push(v))]),
);
// Assert
expect(seenAnswers).toEqual([1, 2]);
}),
);
});✅ 操作建议 当算法接受异步函数作为输入时,编写测试检查竞态条件并测试执行顺序 —— 这可由自动处理
fast-check✅ 操作建议 利用及其任意值测试依赖异步函数的异步代码
fast-checkfc.scheduler()将:
ts
it('should resolve in call order', async () => {
// Arrange
const seenAnswers = [];
const call = vi.fn().mockImplementation((v) => Promise.resolve(v));
// Act
const queued = queue(call);
await Promise.all([queued(1).then((v) => seenAnswers.push(v)), queued(2).then((v) => seenAnswers.push(v))]);
// Assert
expect(seenAnswers).toEqual([1, 2]);
});转换为:
ts
it('should resolve in call order', async () => {
await fc.assert(
fc.asyncProperty(fc.scheduler(), async (s) => {
// Arrange
const seenAnswers = [];
const call = vi.fn().mockImplementation((v) => Promise.resolve(v));
// Act
const queued = queue(s.scheduleFunction(call));
await s.waitFor(
Promise.all([queued(1).then((v) => seenAnswers.push(v)), queued(2).then((v) => seenAnswers.push(v))]),
);
// Assert
expect(seenAnswers).toEqual([1, 2]);
}),
);
});Recommendation for faker users
针对faker用户的建议
If using to fake data, we recommend wiring any fake data generation within by leveraging this code snippet:
fakerfast-checkts
// Source: https://fast-check.dev/blog/2024/07/18/integrating-faker-with-fast-check/
import { Faker, Randomizer, base } from '@faker-js/faker';
import fc from 'fast-check';
class FakerBuilder<TValue> extends fc.Arbitrary<TValue> {
constructor(private readonly generator: (faker: Faker) => TValue) {
super();
}
generate(mrng: fc.Random, biasFactor: number | undefined): fc.Value<TValue> {
const randomizer: Randomizer = {
next: (): number => mrng.nextDouble(),
seed: () => {}, // no-op, no support for updates of the seed, could even throw
};
const customFaker = new Faker({ locale: base, randomizer });
return new fc.Value(this.generator(customFaker), undefined);
}
canShrinkWithoutContext(value: unknown): value is TValue {
return false;
}
shrink(value: TValue, context: unknown): fc.Stream<fc.Value<TValue>> {
return fc.Stream.nil();
}
}
function fakerToArb<TValue>(generator: (faker: Faker) => TValue): fc.Arbitrary<TValue> {
return new FakerBuilder(generator);
}Example of usage
ts
fc.assert(
fc.property(
fakerToArb((faker) => faker.person.firstName),
fakerToArb((faker) => faker.person.lastName),
(firstName, lastName) => {
// code
},
),
);如果使用生成假数据,我们建议通过以下代码片段将假数据生成集成到中:
fakerfast-checkts
// Source: https://fast-check.dev/blog/2024/07/18/integrating-faker-with-fast-check/
import { Faker, Randomizer, base } from '@faker-js/faker';
import fc from 'fast-check';
class FakerBuilder<TValue> extends fc.Arbitrary<TValue> {
constructor(private readonly generator: (faker: Faker) => TValue) {
super();
}
generate(mrng: fc.Random, biasFactor: number | undefined): fc.Value<TValue> {
const randomizer: Randomizer = {
next: (): number => mrng.nextDouble(),
seed: () => {}, // no-op, no support for updates of the seed, could even throw
};
const customFaker = new Faker({ locale: base, randomizer });
return new fc.Value(this.generator(customFaker), undefined);
}
canShrinkWithoutContext(value: unknown): value is TValue {
return false;
}
shrink(value: TValue, context: unknown): fc.Stream<fc.Value<TValue>> {
return fc.Stream.nil();
}
}
function fakerToArb<TValue>(generator: (faker: Faker) => TValue): fc.Arbitrary<TValue> {
return new FakerBuilder(generator);
}使用示例
ts
fc.assert(
fc.property(
fakerToArb((faker) => faker.person.firstName),
fakerToArb((faker) => faker.person.lastName),
(firstName, lastName) => {
// code
},
),
);Equivalence fast-check
and @fast-check/vitest
fast-check@fast-check/vitestfast-check
与@fast-check/vitest
的等价用法
fast-check@fast-check/vitestExample 1.
ts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it('...', ({ g }) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', () => {
fc.assert(
fc.property(fc.gen(), (g) => {
//...
}),
);
});Example 2.
ts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it.prop([...arbitraries])('...', (...values) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', () => {
fc.assert(
fc.property(...arbitraries, (...values) => {
//...
}),
);
});Example 3. If the predicate of or is asynchronous, when using only the property has to be instantiated via and has to be awaited.
itit.propfast-checkasyncPropertyassertts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it.prop([...arbitraries])('...', async (...values) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', async () => {
await fc.assert(
fc.asyncProperty(...arbitraries, async (...values) => {
//...
}),
);
});示例1.
ts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it('...', ({ g }) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', () => {
fc.assert(
fc.property(fc.gen(), (g) => {
//...
}),
);
});示例2.
ts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it.prop([...arbitraries])('...', (...values) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', () => {
fc.assert(
fc.property(...arbitraries, (...values) => {
//...
}),
);
});示例3. 如果或的谓词是异步的,当仅使用时,必须通过实例化属性,并等待完成。
itit.propfast-checkasyncPropertyassertts
// with @fast-check/vitest
import { it, fc } from '@fast-check/vitest';
it.prop([...arbitraries])('...', async (...values) => {
//...
});
// with fast-check
import { it } from 'vitest';
import fc from 'fast-check';
it('...', async () => {
await fc.assert(
fc.asyncProperty(...arbitraries, async (...values) => {
//...
}),
);
});