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.

By The Testing AcademyUpdated July 25, 202617 min read
All field guides
In this guide9 sections
  1. What Is the Difference Between Jest toBe and toEqual?
  2. How Does toBe Use Object.is?
  3. How Do toEqual and toStrictEqual Compare Data?
  4. Which Jest Matcher Fits Each Data Shape?
  5. How Do the Matchers Behave on Edge Values?
  6. Follow a Numbered Matcher-Selection Workflow
  7. Build and Diagnose the Repo Matcher Examples
  8. Frequently Asked Questions About Jest Equality Matchers
  9. What is the main difference between toBe and toEqual in Jest?
  10. Does toBe compare object contents?
  11. When should I use toStrictEqual instead of toEqual?
  12. Why does toBe fail for 0.1 plus 0.2?
  13. Should I compare objects with JSON.stringify?
  14. What is the difference between toContain and toContainEqual?
  15. 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.

JavaScript
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.

JavaScript
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 testPreferred matcherComparison ruleUseful exampleMisleading choice
Exact primitive valuetoBeObject.isexpect(status).toBe('ready')A truthy check that accepts other strings
Same object instancetoBeReference identityCached result is reusedtoEqual, which permits a clone
Recursive data contenttoEqualDeep equalityParsed response shapetoBe on a new object
Exact representationtoStrictEqualDeep equality plus strict detailsRequired undefined key or class instancetoEqual when absence matters
Decimal approximationtoBeCloseToNumeric precisionCalculated ratioExact toBe or toEqual
Primitive array membertoContainItem membershipString in a listManual index boolean
Structured array membertoContainEqualRecursive item equalityObject in a result settoContain with a new literal
Required response subsettoMatchObjectPartial recursive matchStable fields in a large payloadCopying every unrelated field
Generated field with known typeAsymmetric matcher inside toEqualFull shape with flexible leafid: 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.

  1. 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.
  2. Classify the received value. Separate primitives from references, then identify arrays, class instances, maps, sets, errors, promises, and custom domain objects.
  3. Choose identity or structure. Use toBe only if exact primitive value or reference reuse is the requirement. Use a structural matcher for data.
  4. Choose full, strict, or partial shape. Select toEqual, toStrictEqual, or toMatchObject based on whether representation details and unrelated fields belong to the contract.
  5. Handle unpredictable leaves explicitly. Place asymmetric matchers at generated fields instead of deleting evidence or weakening the whole object assertion.
  6. Set numeric tolerance from the domain. Use toBeCloseTo only when an approximate result is valid, and document the chosen precision through the test case.
  7. Run a negative control. Change one meaningful expected value and confirm the test fails for the reason you intended.
  8. 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.

JavaScript
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.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 25, 2026 / Reviewed July 25, 2026

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.

  1. 01
    Official jestjs.io reference

    jestjs.io

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official jestjs.io reference

    jestjs.io

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official tc39.es reference

    tc39.es

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    WebDriver standard

    W3C

    The browser automation protocol specification used by major automation stacks.

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.