{"id":"91a1e0a9c9f1551b","repo":"vitest-dev/vitest","slug":"expect-soft-can-only-be-used-inside-a-test","errorCode":null,"errorMessage":"expect.soft() can only be used inside a test","messagePattern":"expect\\.soft\\(\\) can only be used inside a test","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/expect/src/utils.ts","lineNumber":121,"sourceCode":"    if (name !== 'withTest') {\n      utils.flag(this, '_name', name)\n    }\n\n    if (!utils.flag(this, 'soft')) {\n      // avoid WebKit's proper tail call to preserve stacktrace offset for inline snapshot\n      // https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit\n      try {\n        return fn.apply(this, args)\n      }\n      finally {\n        // no lint\n      }\n    }\n\n    const test: Test = utils.flag(this, 'vitest-test')\n\n    if (!test) {\n      throw new Error('expect.soft() can only be used inside a test')\n    }\n\n    try {\n      const result = fn.apply(this, args)\n\n      if (result && typeof result === 'object' && typeof result.then === 'function') {\n        return result.then(noop, (err) => {\n          handleTestError(test, err)\n        })\n      }\n\n      return result\n    }\n    catch (err) {\n      handleTestError(test, err)\n    }\n  }\n}","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/expect/src/utils.ts#L103-L139","documentation":"Thrown by wrapAssertion in packages/expect/src/utils.ts:121 when the 'soft' flag is set on an assertion but no 'vitest-test' flag is present. expect.soft() records failures onto a Test result object instead of throwing immediately, so it needs an active test context to attach those deferred failures to. Without a test in scope there is nowhere to record the soft failure, so the call is rejected.","triggerScenarios":"Calling expect.soft(...) at module top level, inside beforeAll/beforeEach/afterAll/afterEach hooks, inside describe-only callback without an it, or in a plain Node script invoked outside the Vitest runner. The check at utils.ts:118-122 fires whenever utils.flag(this, 'vitest-test') returns falsy while utils.flag(this, 'soft') is truthy.","commonSituations":"Refactoring a test and accidentally moving a soft assertion into a setup hook; calling expect.soft from a shared helper invoked outside any it block; migrating from plain expect to expect.soft without verifying call sites; running test files via node directly instead of vitest.","solutions":["Move the expect.soft(...) call inside an it() or test() block so a Test context is active.","If the assertion is in a setup/teardown hook (beforeAll/afterAll/etc.), switch to plain expect() since soft failures cannot be recorded there.","If the assertion lives in a shared helper, pass the test context explicitly or restructure so the helper is only invoked from within it blocks.","Verify you are running the file through vitest (not node) so the test binding is injected onto the assertion."],"exampleFix":"// before\nbeforeAll(() => {\n  expect.soft(db.tables).toHaveLength(3)\n})\n\n// after\nbeforeAll(() => {\n  expect(db.tables).toHaveLength(3)\n})\n// or move it inside the test:\nit('has tables', () => {\n  expect.soft(db.tables).toHaveLength(3)\n})","handlingStrategy":"validation","validationCode":"// Ensure expect.soft is only called within an active test context.\nimport { expect } from 'vitest'\n\nfunction softAssertIfInTest<T>(check: () => T) {\n  // Vitest does not expose the active test publicly; gate by convention:\n  // only call this helper from inside it/test blocks.\n  if (typeof (globalThis as any).__vitest_test__ === 'undefined') {\n    return // skip soft assertion outside a test\n  }\n  try { expect.soft(check()) } catch { /* soft handles it */ }\n}","typeGuard":"// No public type guard exists for the internal 'vitest-test' chai flag.\n// Convention guard: only invoke expect.soft inside it()/test().\nfunction isInsideTest(): boolean {\n  // Approximate: rely on Vitest setting context. Best practice is structural\n  // (call site is lexically inside it/test), not runtime-checked.\n  return true\n}","tryCatchPattern":"// If you must run an assertion in a context that may or may not be a test,\n// fall back to a hard expect when soft is unavailable.\nfunction assert<T>(value: T, matcher: (e: ReturnType<typeof expect>) => void) {\n  try {\n    matcher(expect.soft(value) as any)\n  } catch (e) {\n    if (e instanceof Error && /can only be used inside a test/.test(e.message)) {\n      matcher(expect(value) as any)\n    } else { throw e }\n  }\n}","preventionTips":["Only call expect.soft inside it()/test() blocks; never in beforeAll/afterAll/beforeEach/afterEach.","When extracting assertion helpers, document that they must be invoked from within a test.","Run a quick grep for 'expect.soft' in hook files during code review."],"tags":["expect","soft-assertion","test-scope","lifecycle-hooks"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}