PRACTICAL GUIDE / Jest toBe vs toEqual
Jest toBe vs toEqual for Reliable Test Assertions
Jest toBe vs toEqual explains reference identity, deep equality, edge cases, and which matcher produces reliable, useful test failures in CI.
In this guide9 sections
- What Is the Difference Between Jest toBe and toEqual?
- How Does toBe Use Object.is?
- How Do toEqual and toStrictEqual Compare Data?
- Which Jest Matcher Fits Each Data Shape?
- How Do the Matchers Behave on Edge Values?
- Follow a Numbered Matcher-Selection Workflow
- Build and Diagnose the Repo Matcher Examples
- Frequently Asked Questions About Jest Equality Matchers
- What is the main difference between toBe and toEqual in Jest?
- Does toBe compare object contents?
- When should I use toStrictEqual instead of toEqual?
- Why does toBe fail for 0.1 plus 0.2?
- Should I compare objects with JSON.stringify?
- What is the difference between toContain and toContainEqual?
- Conclusion: Make the Matcher State the Contract
What you will learn
- What Is the Difference Between Jest toBe and toEqual?
- How Does toBe Use Object.is?
- How Do toEqual and toStrictEqual Compare Data?
- Which Jest Matcher Fits Each Data Shape?
Jest toBe vs toEqual is a choice between exact identity semantics and recursive
value comparison. toBe applies Object.is, which compares primitive values
directly and requires objects to be the same reference. toEqual walks object
and array contents, so independently created structures can pass when their
contents match. toStrictEqual adds representation checks when missing keys,
sparse arrays, or object types are part of the contract.
That short rule handles most tests, but reliable assertions require one more
decision: what behavior is the test meant to protect? A matcher should encode
that contract precisely and produce a useful difference when it fails. This
guide uses the matcher cases in
src/db/seed-data/challenges/expansion-js-ts.ts, especially the
javascript-testing-jest-matchers-quiz entry in JS_TS_CHALLENGES, and checks
their intended behavior against the current Jest Expect
API and Using Matchers
guide.
What Is the Difference Between Jest toBe and toEqual?
The difference is not that one matcher is always stricter or better. They ask
different questions. toBe asks whether two values are the same according to
Object.is. toEqual asks whether two structures contain recursively equal
values under Jest's equality rules. A good test chooses the question that
matches the production behavior.
Consider a selector that returns the exact cached object stored in application
state. If consumers rely on stable references to avoid repeated work, reference
identity can be observable behavior. expect(selectUser(state)).toBe(state.user)
states that requirement directly. Replacing it with toEqual would allow a
fresh clone and could miss the reference regression.
Now consider an API parser that returns a new object. The allocation is an
implementation detail. The test cares that the parsed identifier, status, and
nested permissions are correct. toEqual expresses that contract without
requiring the parser to reuse the fixture object. The distinction also matters
for fixture design. The guidance on isolated JavaScript test
data explains why
tests often create fresh copies instead of sharing mutable references.
The repository matcher challenge captures this decision with two identical
object literals. They look alike, but each literal allocates a separate object.
toBe fails because the references differ; toEqual passes because the
properties match. That case should appear near the start of the article because
it answers the comparison query without forcing readers through unrelated Jest
setup.
test('distinguishes identity from structure', () => {
const stored = { id: 7, role: 'tester' };
const sameReference = stored;
const sameData = { id: 7, role: 'tester' };
expect(sameReference).toBe(stored);
expect(sameData).not.toBe(stored);
expect(sameData).toEqual(stored);
});For primitive strings, numbers, booleans, null, undefined, symbols, and
bigints, toBe is commonly the clearest exact-value matcher. For objects,
arrays, and functions, use it only when sameness of reference is the intended
result. Broader preparation on value and reference behavior is available in
JavaScript object and class interview
questions,
but this article stays focused on assertion choice.
How Does toBe Use Object.is?
The ECMAScript specification for
Object.is
defines the language operation behind Jest's toBe. It resembles strict
equality for many everyday values, but two numeric edge cases are different:
Object.is(NaN, NaN) is true, and Object.is(0, -0) is false. Jest inherits
those results.
That makes toBe suitable for exact primitive outcomes and deliberate
identity checks. It is also clearer than wrapping a comparison in another
boolean assertion. expect(actual).toBe(expected) lets Jest report the
received and expected values. expect(actual === expected).toBe(true) collapses
the useful evidence to false versus true, leaving the engineer to recreate
the operands.
Reference assertions are most useful at boundaries where identity carries a contract:
- A cache returns the previously stored instance for the same key.
- A reducer returns the original state object when an action changes nothing.
- A memoized function reuses its result while inputs are unchanged.
- A registry returns the exact object that was registered.
- An event subscription removes the exact callback that was added.
They are less useful for parsed JSON, database rows, network responses, and factory output, where new allocations are normal. An assertion that happens to pass because the test reuses its fixture can accidentally pin an implementation detail. Immutable test data architecture helps keep fixture mutation separate from the decision about identity.
toBe does not inspect an object and then decide that its fields are equal. If
the failure output suggests serializing an object for a deep comparison, that
is a clue that the assertion probably asked an identity question when it meant
to ask a structural one. Change the matcher only after confirming that object
contents, rather than object reuse, are the actual requirement.
Object.is also does not introduce numeric tolerance. Exact comparison remains
exact. The fact that it recognizes NaN as the same value does not make it
suitable for rounding differences. For measurements or calculations where
small representation error is acceptable, use toBeCloseTo and choose a
precision that reflects the domain.
How Do toEqual and toStrictEqual Compare Data?
toEqual recursively checks fields in objects and items in arrays. It is the
usual choice for data transfer objects, parsed payloads, configuration values,
and function results whose content matters more than allocation. It gives Jest
enough structure to show a focused diff when a nested value changes.
The current Jest guide documents three important cases that toEqual does not
treat as differences: object keys whose value is undefined, undefined array
items or array sparseness, and object type mismatches. toStrictEqual includes
those representation details. This is why calling toStrictEqual merely "more
strict" is incomplete. The question is whether those details belong to the
contract.
Suppose an update payload uses absence to mean "leave the existing role alone"
and an explicit role: undefined value is invalid. A test using toEqual may
not protect that distinction. toStrictEqual can. The same idea applies when a
sparse array indicates missing samples while an explicit undefined item means
a sample was collected without a value.
Class identity can matter when methods, validation, or serialization depend on
the prototype. A plain object with matching fields is not necessarily a valid
domain instance. If the function promises to return an Invoice instance,
assert that type and consider toStrictEqual for its exact data. If the
function promises only a serializable invoice shape, requiring a particular
class may over-specify the implementation.
class UserRecord {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
test('chooses equality based on the representation contract', () => {
expect({ name: 'Ada', role: undefined }).toEqual({ name: 'Ada' });
expect({ name: 'Ada', role: undefined }).not.toStrictEqual({ name: 'Ada' });
expect([, 'ready']).toEqual([undefined, 'ready']);
expect([, 'ready']).not.toStrictEqual([undefined, 'ready']);
expect(new UserRecord('Ada', 'admin')).toEqual({
name: 'Ada',
role: 'admin',
});
expect(new UserRecord('Ada', 'admin')).not.toStrictEqual({
name: 'Ada',
role: 'admin',
});
});TypeScript can prevent some shape mistakes before runtime, but compile-time types do not replace runtime assertions over external data. Runtime schema validation with static TypeScript models covers that boundary. When assertion helpers need to narrow a type after checking it, see TypeScript control-flow narrowing for assertion helpers.
Which Jest Matcher Fits Each Data Shape?
A matcher-selection table makes the contract visible before code review turns into a debate about personal style. The "misleading choice" column does not mean the matcher is invalid everywhere. It identifies a common mismatch between the stated contract and the assertion.
| Contract under test | Preferred matcher | Comparison rule | Useful example | Misleading choice |
|---|---|---|---|---|
| Exact primitive value | toBe | Object.is | expect(status).toBe('ready') | A truthy check that accepts other strings |
| Same object instance | toBe | Reference identity | Cached result is reused | toEqual, which permits a clone |
| Recursive data content | toEqual | Deep equality | Parsed response shape | toBe on a new object |
| Exact representation | toStrictEqual | Deep equality plus strict details | Required undefined key or class instance | toEqual when absence matters |
| Decimal approximation | toBeCloseTo | Numeric precision | Calculated ratio | Exact toBe or toEqual |
| Primitive array member | toContain | Item membership | String in a list | Manual index boolean |
| Structured array member | toContainEqual | Recursive item equality | Object in a result set | toContain with a new literal |
| Required response subset | toMatchObject | Partial recursive match | Stable fields in a large payload | Copying every unrelated field |
| Generated field with known type | Asymmetric matcher inside toEqual | Full shape with flexible leaf | id: expect.any(String) | Deleting the generated field |
Use toMatchObject when the test owns only part of a larger response contract.
It should not become a habit for avoiding exact assertions. If every field is
business-critical, an exact structural assertion is clearer. If only status
and courier determine the behavior under test, requiring thirty unrelated
fields makes the test respond to changes outside its purpose.
Asymmetric matchers preserve a full expected shape while allowing a value that cannot be known in advance. They are useful for generated identifiers, timestamps with a separately tested format, and strings that must contain a known fragment. They are stronger than mutating the received object to delete unpredictable fields because the assertion still verifies those fields exist and satisfy a stated rule.
Collections have their own semantics. toContain is natural for strings and
primitive items. toContainEqual is appropriate for structured members.
Maps, sets, weak collections, and iteration rules introduce additional
questions covered in JavaScript Map, Set, WeakMap, and WeakSet for
tests.
Errors, dates, regular expressions, maps, and sets deserve deliberate examples before a team standardizes one matcher for all objects. They are objects, but their meaningful state is not limited to ordinary enumerable fields. The current Jest Expect API documents the equality behavior it supports, and a project should verify the exact runner version for custom equality testers or domain classes. Do not infer that every object with no visible enumerable properties is equal.
Error assertions are a good illustration. If the contract is that a function
throws, toThrow states that directly and can check an error class or message.
Capturing an error and comparing it as a generic object may omit the semantic
part a reviewer needs. If the contract includes a custom error code and cause,
catch the error deliberately and assert those fields in addition to its class.
Matcher choice should follow the observable behavior, not the JavaScript
typeof category alone.
Dates also need a domain decision. toEqual can compare date values, but the
test must first decide whether it owns an exact instant, a calendar date in a
named zone, or a formatted string. Converting both dates to strings inside the
assertion can accidentally include local time-zone formatting. Normalize at
the application boundary the test is intended to protect, then compare the
result with the matcher that states that boundary.
When a domain object has meaningful equality beyond its fields, consider testing its public observations rather than copying implementation state into a large literal. A money value might expose currency and minor units; a URL may be compared through normalized components; an error may expose type, code, and cause. Custom equality testers exist, but introducing one changes comparison semantics across assertions and should be reviewed as test infrastructure, not added merely to shorten one expected object.
How Do the Matchers Behave on Edge Values?
Edge values expose whether a test is asserting a domain rule or repeating a
language operation without thought. The repository challenge includes the
classic 0.1 + 0.2 example because switching from toBe to toEqual does not
fix it. Both perform exact numeric comparison in that situation. toBeCloseTo
is the intended matcher when approximation is acceptable.
Precision is a product decision. A UI percentage, a scientific measurement, and an account balance do not share one tolerance. For exact decimal domains, the implementation may use integer minor units or a decimal library, and the test should assert that exact representation. Do not hide a real rounding defect by widening precision until the test passes.
NaN is another useful case. expect(value).toBe(NaN) can pass because
Object.is(NaN, NaN) is true, but toBeNaN() states the expectation more
directly. Likewise, toBeNull, toBeUndefined, and toBeDefined communicate
intent better than a generic exact comparison when absence is the behavior.
Signed zero rarely matters in application code, but it can matter in numerical
algorithms because 1 / 0 is positive infinity while 1 / -0 is negative
infinity. toBe distinguishes them. If the domain treats both as equivalent,
normalize the result or assert the relevant domain behavior rather than
selecting a matcher solely to force equality.
Strings can look equal to a reader while containing different Unicode
sequences. Neither toBe nor toEqual normalizes text. They compare the
actual strings they receive. When normalization is part of the requirement,
test it explicitly. Reliable Unicode string
assertions
provides focused examples for that problem.
Sparse arrays and explicit undefined elements are a representation decision,
not trivia. A sparse slot is absent, while an explicit undefined element is
present. toEqual can treat them alike; toStrictEqual distinguishes them.
Use the matcher that reflects whether consumers observe array shape through
methods, serialization, or index ownership.
Follow a Numbered Matcher-Selection Workflow
A consistent workflow prevents matcher choice from being driven by autocomplete. Run it while writing the test, then repeat it when a failure suggests the assertion protects the wrong detail.
- Write the behavior in one sentence. State what a caller can observe: exact value, same instance, full data shape, required subset, approximate number, or collection membership.
- Classify the received value. Separate primitives from references, then identify arrays, class instances, maps, sets, errors, promises, and custom domain objects.
- Choose identity or structure. Use
toBeonly if exact primitive value or reference reuse is the requirement. Use a structural matcher for data. - Choose full, strict, or partial shape. Select
toEqual,toStrictEqual, ortoMatchObjectbased on whether representation details and unrelated fields belong to the contract. - Handle unpredictable leaves explicitly. Place asymmetric matchers at generated fields instead of deleting evidence or weakening the whole object assertion.
- Set numeric tolerance from the domain. Use
toBeCloseToonly when an approximate result is valid, and document the chosen precision through the test case. - Run a negative control. Change one meaningful expected value and confirm the test fails for the reason you intended.
- Read the diff as a maintainer. If the output hides the relevant field, replace boolean wrappers, serialized strings, or oversized fixtures with a more direct assertion.
This process fits naturally inside a focused unit layer described by the test pyramid. It also supports either a test-first or behavior-first workflow; BDD versus TDD provides the wider development context without changing the matcher semantics.
Build and Diagnose the Repo Matcher Examples
The javascript-testing-jest-matchers-quiz seed in
src/db/seed-data/challenges/expansion-js-ts.ts offers a useful progression:
object identity, floating point, strict equality, thrown errors, array
membership, subset matching, and asymmetric values. An article example should
preserve that progression while turning each quiz answer into a small test
whose failure can be inspected.
The following sample combines the response-shape cases. It asserts selected
stable top-level fields, permits a generated identifier with a known type,
checks that items is an array, and verifies that the list contains one
recursively equal object. It does not assert the complete list or prohibit
additional items. It also does not mutate the response or convert it to JSON
before comparing.
test('returns a shipped order with a generated identifier', () => {
const response = {
id: 'ord_9f3',
status: 'shipped',
courier: 'BlueDart',
items: [
{ sku: 'BOOK-1', quantity: 1 },
{ sku: 'PEN-2', quantity: 2 },
],
};
expect(response).toEqual({
id: expect.any(String),
status: 'shipped',
courier: 'BlueDart',
items: expect.any(Array),
});
expect(response.items).toContainEqual({
sku: 'PEN-2',
quantity: 2,
});
});To test the diagnostic quality, change quantity from 2 to 3. Jest should
point to the mismatched field inside the expected object. A weaker assertion
such as expect(response.items.some(...)).toBe(true) reports only a boolean
failure unless the condition is carefully instrumented. Direct matchers leave
the runner with more evidence.
Failure output is part of assertion design. A test will be read during an incident or code review, often by someone who did not write it. A focused diff should answer three questions: which contract failed, what value arrived, and what value or rule was expected. Large copied payloads can bury the relevant field, while partial matchers can omit a field that should have been protected. The right balance follows test ownership.
A deliberate failure review is useful before merging a new assertion pattern. Change one stable field, remove one required property, and replace one generated value with the wrong type. Confirm that each failure names the relevant path and does not drown it in unrelated payload data. Then restore the fixture. This small exercise tests the diagnostic contract without adding a production claim or a permanent broken case.
Keep received and expected values in their natural positions. Jest can still
evaluate a reversed assertion, but its messages become harder to read because
the expected side contains the dynamic result. Prefer
expect(actual).toEqual(expected). Give fixtures domain names such as
expectedOrder instead of data2, and avoid computing the expected object
through the same transformation used by production code. Shared logic can make
both sides wrong in the same way.
Large arrays deserve focused membership or property assertions when ordering is outside the contract. If order matters, assert the ordered array directly. If order does not matter, select a documented unordered strategy rather than sorting the received value in place, which mutates evidence. A copied and sorted representation may be acceptable when the test explicitly owns that normalization, but it should be visible in the arrangement rather than hidden inside a boolean expression.
Custom matchers can improve repeated domain diagnostics, but they are a later step. First use built-in matchers accurately. When repetition proves that a domain rule deserves one named assertion, compare the design with Playwright custom expect matchers, while remembering that its runner and extension API differ from Jest.
Frequently Asked Questions About Jest Equality Matchers
What is the main difference between toBe and toEqual in Jest?
toBe uses Object.is. It compares primitive values exactly and objects by
reference identity. toEqual recursively compares object fields and array
items. Choose toBe for exact primitive results or intentional instance reuse,
and choose toEqual for independently created data structures whose contents
must match.
Does toBe compare object contents?
No. Two literals such as { id: 1 } and { id: 1 } are different objects, so
toBe fails even though their fields look identical. It passes only when both
operands point to the same object. Use toEqual when the allocation itself is
not part of the behavior.
When should I use toStrictEqual instead of toEqual?
Use toStrictEqual when missing versus explicitly undefined properties,
sparse versus explicit array entries, or class versus plain-object identity
must remain distinct. Use toEqual when those representation details are
irrelevant and the recursive values are the contract.
Why does toBe fail for 0.1 plus 0.2?
The computed binary floating-point value is not exactly decimal 0.3.
toEqual is not a fix because it also compares numbers exactly in this case.
Use toBeCloseTo with domain-appropriate precision, or represent values in an
exact form when the business rule does not allow approximation.
Should I compare objects with JSON.stringify?
Usually not. Serialization can omit undefined values, transform special
values, and make ordering or representation part of the assertion. It also
reduces Jest's ability to show a structural diff. Prefer toEqual,
toStrictEqual, toMatchObject, and asymmetric matchers based on the actual
contract.
What is the difference between toContain and toContainEqual?
toContain is appropriate for primitive members or the same object reference.
toContainEqual recursively compares a candidate item, so it can find an
object whose contents match a separately created expected object. It is the
natural choice for structured items in arrays.
Conclusion: Make the Matcher State the Contract
The reliable Jest toBe vs toEqual decision starts with observable behavior.
Use toBe for exact primitive outcomes and intentional reference identity.
Use toEqual for recursive data content, then move to toStrictEqual when
representation details are meaningful. Reach for specialized matchers when
the contract is numeric tolerance, partial shape, generated leaves, or
structured membership.
Do not switch matchers merely to turn a red test green. Name the contract, run a negative control, and inspect the resulting diff. That discipline keeps assertions sensitive to real regressions without binding tests to irrelevant allocation or serialization details. For continued practice, the advanced JavaScript automation questions place equality choices inside wider test-design scenarios.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Official jestjs.io reference
jestjs.io
Primary documentation selected and verified for the claims in this guide.
- 02Official jestjs.io reference
jestjs.io
Primary documentation selected and verified for the claims in this guide.
- 03Official tc39.es reference
tc39.es
Primary documentation selected and verified for the claims in this guide.
- 04
FAQ / QUICK ANSWERS
Questions testers ask
What is the main difference between toBe and toEqual in Jest?
toBe applies Object.is to the received and expected values. That means primitives are compared by value while objects must be the same reference. toEqual recursively compares object and array contents, so separately created data structures can pass when their relevant fields and nested values match.
Does toBe compare object contents?
No. For objects, functions, and arrays, toBe checks whether both operands refer to the same value in memory. Two object literals with identical properties are separate references and fail. Use toEqual for recursive content comparison unless reference identity is the behavior the test is meant to protect.
When should I use toStrictEqual instead of toEqual?
Use toStrictEqual when the exact data representation matters. It distinguishes missing keys from keys explicitly set to undefined, sparse array holes from explicit undefined elements, and class instances from plain objects. Use toEqual when those representation details are outside the contract and only recursive values matter.
Why does toBe(0.3) fail for 0.1 plus 0.2?
Binary floating-point arithmetic cannot represent every decimal fraction exactly, so the computed value is slightly different from 0.3. toBe and toEqual do exact numeric comparison here. Use toBeCloseTo with a precision suited to the domain, while keeping money and other exact decimal rules explicit.
Should I compare objects with JSON.stringify in Jest?
Usually no. String comparison can make property ordering and serialization rules part of the test while losing Jest's focused structural diff. It also omits or transforms some JavaScript values. Use toEqual, toStrictEqual, toMatchObject, or asymmetric matchers so the assertion states the intended data contract directly.
What is the difference between toContain and toContainEqual?
For array items, toContain looks for the same value using strict-style item comparison, so a newly created object does not match an equal-looking object already in the array. toContainEqual compares an item recursively. Use toContain for primitives or shared references and toContainEqual for structured values.
RELATED GUIDES
Continue the learning route
GUIDE 01
JavaScript Unicode Strings for Reliable Test Assertions
Master JavaScript unicode string testing with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 02
TypeScript Control Flow Narrowing for Assertion Helpers
Master TypeScript narrowing assertion helpers with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 03
JavaScript Object and Class Interview Questions for SDETs
JavaScript objects classes interview questions: practical design, implementation, debugging, CI, metrics, and interview guidance for QA, SDET, and automation engineers.
GUIDE 04
Immutable Test Data Architecture in JavaScript
JavaScript immutable test data architecture: practical design, implementation, debugging, CI, metrics, and interview guidance for QA, SDET, and automation engineers.
GUIDE 05
Custom Playwright Matchers: Extend and Merge expect Safely
Create retry-aware Playwright custom matchers, merge expect modules without collisions, and preserve useful negation, timeout, and failure output.