Overview
The _unsafeUnwrap() and _unsafeUnwrapErr() methods are designed exclusively for testing. They extract the inner value from a Result without proper error handling, throwing an error if the wrong variant is accessed.
Test Environments OnlyThese methods should never be used in production code. The underscore prefix is a convention indicating they are unsafe. In production, always use safe methods like match(), unwrapOr(), or proper error handling.
Type Signatures
class Result<T, E> {
_unsafeUnwrap(config?: ErrorConfig): T
_unsafeUnwrapErr(config?: ErrorConfig): E
}
ErrorConfig
type ErrorConfig = {
withStackTrace?: boolean
}
_unsafeUnwrap()
Extracts the value from an Ok variant. Throws if called on an Err.
Behavior
- If
Result is Ok: Returns the inner value T
- If
Result is Err: Throws a custom error object
Basic Usage
import { ok, err } from 'neverthrow'
const okResult = ok(42)
console.log(okResult._unsafeUnwrap()) // 42
const errResult = err('Something went wrong')
console.log(errResult._unsafeUnwrap()) // Throws!
In Tests
import { expect, test } from 'vitest'
import { processUser } from './user-service'
test('processUser returns valid user', () => {
const result = processUser({ name: 'John', email: '[email protected]' })
// Extract value for assertion
const user = result._unsafeUnwrap()
expect(user.name).toBe('John')
expect(user.email).toBe('[email protected]')
})
_unsafeUnwrapErr()
Extracts the error from an Err variant. Throws if called on an Ok.
Behavior
- If
Result is Err: Returns the inner error E
- If
Result is Ok: Throws a custom error object
Basic Usage
import { ok, err } from 'neverthrow'
const errResult = err('Something went wrong')
console.log(errResult._unsafeUnwrapErr()) // 'Something went wrong'
const okResult = ok(42)
console.log(okResult._unsafeUnwrapErr()) // Throws!
In Tests
import { expect, test } from 'vitest'
import { validateEmail } from './validators'
test('validateEmail returns error for invalid email', () => {
const result = validateEmail('not-an-email')
// Extract error for assertion
const error = result._unsafeUnwrapErr()
expect(error.type).toBe('InvalidEmail')
expect(error.message).toContain('invalid format')
})
Stack Traces
By default, thrown errors don’t include stack traces. This makes Jest error messages cleaner and easier to read.
Enable Stack Traces
const result = err('Error')
// Without stack trace (default)
try {
result._unsafeUnwrap()
} catch (e) {
console.log(e.stack) // undefined
}
// With stack trace
try {
result._unsafeUnwrap({ withStackTrace: true })
} catch (e) {
console.log(e.stack) // Full stack trace
}
Testing Patterns
Testing Success Cases
import { expect, test } from 'vitest'
import { calculateTotal } from './calculator'
test('calculates total correctly', () => {
const result = calculateTotal([10, 20, 30])
expect(result._unsafeUnwrap()).toBe(60)
})
test('returns zero for empty array', () => {
const result = calculateTotal([])
expect(result._unsafeUnwrap()).toBe(0)
})
Testing Error Cases
import { expect, test } from 'vitest'
import { divide } from './calculator'
test('returns error when dividing by zero', () => {
const result = divide(10, 0)
expect(result._unsafeUnwrapErr()).toBe('DivisionByZero')
})
test('returns detailed error for invalid input', () => {
const result = processInput('invalid')
const error = result._unsafeUnwrapErr()
expect(error.code).toBe('INVALID_INPUT')
expect(error.details).toContain('expected number')
})
Testing with safeTry
import { expect, test } from 'vitest'
import { safeTry, ok, err } from 'neverthrow'
test('safeTry returns first error', () => {
const result = safeTry(function*() {
const a = yield* ok(1)
const b = yield* err('SecondError')
const c = yield* ok(3)
return ok(a + b + c)
})
expect(result._unsafeUnwrapErr()).toBe('SecondError')
})
test('safeTry returns ok when all succeed', () => {
const result = safeTry(function*() {
const a = yield* ok(1)
const b = yield* ok(2)
return ok(a + b)
})
expect(result._unsafeUnwrap()).toBe(3)
})
Alternative: Compare Results Directly
You don’t always need to unwrap Results in tests. Result instances are comparable:
import { expect, test } from 'vitest'
import { ok, err } from 'neverthrow'
import { processUser } from './user-service'
test('processUser returns expected result', () => {
const result = processUser({ name: 'John' })
// Compare Result directly (no unwrapping needed)
expect(result).toEqual(ok({ id: '123', name: 'John' }))
})
test('processUser returns error for invalid input', () => {
const result = processUser({ name: '' })
// Compare Error directly
expect(result).toEqual(err('InvalidName'))
})
With Jest/Vitest Matchers
import { expect, test } from 'vitest'
import { processData } from './processor'
test('processes data successfully', () => {
const result = processData('input')
// Use custom matchers if available
expect(result.isOk()).toBe(true)
// Or unwrap for detailed assertions
const data = result._unsafeUnwrap()
expect(data).toHaveProperty('processed', true)
expect(data.value).toBeGreaterThan(0)
})
Advanced Testing Patterns
Testing Complex Error Types
import { expect, test } from 'vitest'
type ValidationError =
| { type: 'TooShort'; minLength: number }
| { type: 'TooLong'; maxLength: number }
| { type: 'InvalidFormat'; pattern: string }
test('validatePassword returns correct error type', () => {
const result = validatePassword('abc')
const error = result._unsafeUnwrapErr()
expect(error.type).toBe('TooShort')
if (error.type === 'TooShort') {
expect(error.minLength).toBe(8)
}
})
Testing Async Results
import { expect, test } from 'vitest'
import { fetchUser } from './api'
test('fetchUser returns user data', async () => {
const resultAsync = fetchUser('123')
const result = await resultAsync
const user = result._unsafeUnwrap()
expect(user.id).toBe('123')
expect(user.name).toBeDefined()
})
test('fetchUser handles not found', async () => {
const resultAsync = fetchUser('nonexistent')
const result = await resultAsync
const error = result._unsafeUnwrapErr()
expect(error).toBe('NotFound')
})
Snapshot Testing
import { expect, test } from 'vitest'
import { generateReport } from './reports'
test('generateReport matches snapshot', () => {
const result = generateReport({ startDate: '2024-01-01', endDate: '2024-01-31' })
const report = result._unsafeUnwrap()
expect(report).toMatchSnapshot()
})
ESLint Plugin
Use eslint-plugin-neverthrow to enforce proper Result handling:
{
"plugins": ["neverthrow"],
"rules": {
"neverthrow/must-use-result": "error"
}
}
This ensures you either:
- Call
.match()
- Call
.unwrapOr()
- Call
._unsafeUnwrap() (only in tests)
Why the Underscore Prefix?
The underscore (_) prefix is a naming convention that signals:
- This method is unsafe
- It should only be used in specific contexts (tests)
- It breaks the normal error handling guarantees
- Production code should avoid it
This convention is borrowed from languages like Rust, where unsafe operations are clearly marked.
Key Points
- Only use in tests - Never in production code
_unsafeUnwrap() extracts Ok values, throws on Err
_unsafeUnwrapErr() extracts Err values, throws on Ok
- Stack traces are disabled by default for cleaner test output
- Enable stack traces with
{ withStackTrace: true }
- Consider comparing Results directly instead of unwrapping
- Use ESLint plugin to enforce safe usage
- The underscore prefix indicates unsafe operation