Vitest の assert を使って不要な if 文を避ける tips
zod などで作成した schema に対して Vitest で Unit テストを記述する際に以下のような if 文を書くことがあります。
import { z } from 'zod';
export const userSchema = z.object({
name: z.string().min(1, "Name is required"),
});
import { z } from 'zod';
import { test, expect } from 'vitest';
test('parseに失敗すること', () => {
const result = userSchema.safeParse({
name: ""
});
expect(result.success).toBe(false);
// expect.toBeでは型の絞り込みがされないため、if文でresult.successがfalseであることを絞り込む
if (result.success) {
throw new Error();
}
const tree = z.treeifyError(result.error);
expect(tree.properties?.name?.errors[0]).toBe("Name is required");
});
result.error に ! を使用して絞り込みをしないようにすることも可能ですが、lint ルールなどで ! の使用を禁止している場合はできません。
そのような時に Vitest の assert を使用します。
assertを使用したコード
if 文の箇所を assert に変えることで型を絞ることができます。
import { z } from 'zod';
import { test, assert, expect } from 'vitest';
test('parseに失敗すること', () => {
const result = userSchema.safeParse({
name: ""
});
expect(result.success).toBe(false);
// result.successがfalseとして型が絞り込まれる
assert(!result.success);
const tree = z.treeifyError(result.error);
expect(tree.properties?.name?.errors[0]).toBe("Name is required");
});
assert に失敗した場合は以下のようなエラーが発生します。

assert と expect の使い分け
assert(!result.success); を行うことで expect(result.success).toBe(false); が不要になったと思う方もいるかもしれません。しかし、assert に失敗した場合、エラーが発生するため、テストログがみづらくなってしまいます。
そのため、assert でエラーが発生しまう可能性がある場合は前段で expect を挟むのが良いでしょう。以下は expect が失敗した時のスクショです。

さまざまな assert
Vitest の assert は122種類あります(実際には chai の assert をそのまま再exportしているだけなので chai の assert ですが)。
assert.isFalse
https://vitest.dev/api/assert.html#isfalse
対象の値が false であることを判定する assert です。
import { assert, test } from 'vitest'
const testPassed = false
test('assert.isFalse', () => {
assert.isFalse(testPassed)
})
先ほどのサンプルコードでは assert(!result.success); と書いて、result.success が false であることを判定していましたが、assert.isFalse を使うことで assert.isFalse(result.success); と書けるため、可読性が向上します。
assert.operator
https://vitest.dev/api/assert.html#operator
第二引数に演算子を書いてそれで評価する assert です(いつ使うんでしょう)。
import { assert, test } from 'vitest'
test('assert.operator', () => {
assert.operator(1, '<', 2, 'everything is ok')
})
内部実装はどうなっているのか見てみたらとても愚直で微笑みました。
assert.changes
https://vitest.dev/api/assert.html#changes
対象のオブジェクトのプロパティが変更されたことを判定する assert です(いつ使うんでしょう)。
import { assert, test } from 'vitest'
test('assert.changes', () => {
const obj = { val: 10 }
function fn() { obj.val = 22 };
assert.changes(fn, obj, 'val')
})
サンプルコードでは test ブロック内で作成した obj と fn を使っているため、実務上での使いどころは想像できませんでした。
おわり
Vitest の assert を使うことでテストコードをシンプルにし、可読性を高めることができるのでおすすめです。
Discussion
assert とどちらが優れているかは分かりませんが、僕は expect.unreachable を使っています!
expect.unreachable、初耳ですがそっちの方がセマンティックに見えますね
assertはArrange(準備)の段階とかで使うのが良いのかもなーと思いました!
なるほど、それが良さそうですね!