The Jest Functions You’ll Actually Use

A practical reference to the Jest functions that come up in almost every test suite — with a short example for each. New to Jest? Read top to bottom. Just looking something up? Jump to the section you need.

All examples assume a simple module under test:

// math.js
function add(a, b) { return a + b; }
function divide(a, b) {
  if (b === 0) throw new Error("Cannot divide by zero");
  return a / b;
}
module.exports = { add, divide };

1. Structure: organizing your tests

test() / it() — define a single test

it() is just an alias for test(). Use whichever reads better; many people prefer it() because it makes the sentence flow (“it returns the sum”).

test('adds two numbers', () => {
  expect(add(2, 3)).toBe(5);
});

it('returns the sum of two numbers', () => {
  expect(add(2, 3)).toBe(5);
});

describe() — group related tests

Groups tests into a block so the output is readable and shared setup is scoped.

describe('add', () => {
  test('adds positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('handles negatives', () => {
    expect(add(-1, -1)).toBe(-2);
  });
});

Setup and teardown: beforeEach / afterEach / beforeAll / afterAll

beforeEach / afterEach run around every test in scope. beforeAll / afterAll run once for the whole block. Use the “each” variants to reset state between tests; use the “all” variants for expensive one-time setup like a DB connection.

describe('shopping cart', () => {
  let cart;

  beforeEach(() => {
    cart = [];            // fresh state for every test
  });

  afterEach(() => {
    cart = null;          // cleanup
  });

  test('starts empty', () => {
    expect(cart).toHaveLength(0);
  });
});

2. Assertions: expect() and matchers

expect(value) wraps the value you’re testing, then you chain a matcher that describes what you expect. You can negate any matcher with .not.

expect(add(2, 2)).toBe(4);
expect(add(2, 2)).not.toBe(5);

Equality

MatcherUse for
toBe()Strict equality (===) — primitives like numbers, strings, booleans
toEqual()Deep equality — objects and arrays (compares contents)
toStrictEqual()Like toEqual() but also checks types and undefined properties
expect(2 + 2).toBe(4);                          // primitive
expect({ a: 1 }).toEqual({ a: 1 });             // objects — toBe would FAIL here
expect([1, 2]).toEqual([1, 2]);                 // arrays
expect({ a: 1 }).toStrictEqual({ a: 1 });       // strictest

Gotcha: toBe() on two separate objects always fails because they’re different references in memory. Use toEqual() for anything that isn’t a primitive.

Truthiness

expect(true).toBeTruthy();
expect(0).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('hello').toBeDefined();

Numbers

expect(10).toBeGreaterThan(5);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3);   // float math — never use toBe here

toBeCloseTo() exists because 0.1 + 0.2 === 0.30000000000000004 in JavaScript. Use it for any floating-point comparison.

Strings and arrays

expect('team work').toContain('work');         // substring
expect(['a', 'b', 'c']).toContain('b');        // array membership
expect([1, 2, 3]).toHaveLength(3);
expect('2024-06-22').toMatch(/^\d{4}-\d{2}-\d{2}$/);  // regex

Objects

const user = { name: 'Vishal', roles: { admin: true } };
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('name', 'Vishal');     // key + value
expect(user).toHaveProperty('roles.admin', true);  // nested path

Errors: toThrow()

The most common Jest mistake lives here. You must pass a function, not the result of calling it — otherwise the error throws before Jest can catch it.

// WRONG — throws immediately, test errors out
expect(divide(10, 0)).toThrow();

// RIGHT — wrap in an arrow function so Jest controls when it runs
expect(() => divide(10, 0)).toThrow();
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');  // substring match
expect(() => divide(10, 0)).toThrow(Error);                    // by type

3. Async tests

A function that returns a promise needs special handling, or the test will pass before the promise settles.

async / await

async function fetchUser(id) {
  if (id < 0) throw new Error("Invalid id");
  return { id, name: "Alice" };
}

test('fetches a user', async () => {
  const user = await fetchUser(1);
  expect(user).toEqual({ id: 1, name: 'Alice' });
});

resolves / rejects

A cleaner style for asserting directly on a promise. Don’t forget the await — without it, a failing assertion can slip through silently.

test('resolves to a user', async () => {
  await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

test('rejects on invalid id', async () => {
  await expect(fetchUser(-1)).rejects.toThrow('Invalid id');
});

4. Mocks

Mocks let you replace real dependencies (network calls, timers, other modules) with fakes you control, and then assert how they were used.

jest.fn() — a standalone mock function

test('calls the callback', () => {
  const callback = jest.fn();

  [1, 2, 3].forEach(callback);

  expect(callback).toHaveBeenCalled();
  expect(callback).toHaveBeenCalledTimes(3);
  expect(callback).toHaveBeenCalledWith(1, 0, [1, 2, 3]);
});

You can also control its return value:

const getId = jest.fn().mockReturnValue(42);
expect(getId()).toBe(42);

const fetchData = jest.fn().mockResolvedValue({ ok: true });
await expect(fetchData()).resolves.toEqual({ ok: true });

jest.spyOn() — watch (and optionally replace) a real method

Useful when you want to track calls to an existing method without rewriting it. Restore it afterward so other tests aren’t affected.

test('logs a warning', () => {
  const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});

  console.warn('careful');

  expect(spy).toHaveBeenCalledWith('careful');
  spy.mockRestore();   // put the real method back
});

jest.mock() — replace an entire module

Mock out a whole dependency, like an HTTP client, so your tests never hit the network.

jest.mock('axios');
const axios = require('axios');

test('fetches users from the API', async () => {
  axios.get.mockResolvedValue({ data: [{ id: 1 }] });

  const res = await axios.get('/users');

  expect(res.data).toEqual([{ id: 1 }]);
  expect(axios.get).toHaveBeenCalledWith('/users');
});

Mock matchers at a glance

expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenLastCalledWith('latest');

Quick reference

CategoryFunctions
Structuredescribetest / itbeforeEachafterEachbeforeAllafterAll
EqualitytoBetoEqualtoStrictEqual
TruthinesstoBeTruthytoBeFalsytoBeNulltoBeUndefinedtoBeDefined
NumberstoBeGreaterThantoBeLessThantoBeCloseTo
Strings/arraystoContaintoHaveLengthtoMatch
ObjectstoHavePropertytoEqual
ErrorstoThrow
Asyncasync/awaitresolvesrejects
Mocksjest.fnjest.spyOnjest.mock
Mock matcherstoHaveBeenCalledtoHaveBeenCalledTimestoHaveBeenCalledWith

Three mistakes worth memorizing: use toEqual (not toBe) for objects and arrays; wrap throwing calls in an arrow function for toThrow; and always await your resolves / rejects assertions.

Introducing My WooCommerce Product Table Plugin 🚀

Over the last few months, I’ve been working deeply with WooCommerce — especially in scenarios where product catalogs are large, complex, and performance-sensitive.

One recurring problem stood out:

👉 The default WooCommerce grid layout breaks down when users need speed, comparison, and bulk actions.

So I built something to solve that properly.

Today, I’m open-sourcing it:

👉 https://github.com/vishalkakadiya/wc-product-table/


💡 The Problem

WooCommerce works great for standard stores.

But when you move into:

  • Wholesale / B2B
  • Bulk ordering systems
  • Course or product catalogs
  • Restaurants / menus

Users don’t want to “browse” — they want to:

✔ Scan quickly
✔ Search instantly
✔ Add multiple items fast

A table UI fits this use case far better than grids.


⚙️ What This Plugin Does

This plugin lets you display WooCommerce products in a fully interactive table with:

  • Search
  • Sorting
  • Filtering
  • Pagination
  • AJAX cart actions

All optimized for speed and usability.


🔥 Key Features

Here are the highlights that make it powerful:

⚡ AJAX Add to Cart (Single + Bulk)

Add products without page reload — including bulk add-to-cart functionality.

🧩 Variable Product Support

Select variations directly inside the table — no need to visit product pages.

🔎 Search, Filter & Sort

  • Real-time search
  • Category & tag filters
  • Sortable columns (price, name, etc.)

📄 Pagination with Control

AJAX-powered pagination with customizable per-page limits.

🔗 External/Affiliate Support

Handles affiliate products cleanly with proper CTA behavior.


🧠 Built for Developers (This Matters)

This is not just a UI plugin — it’s built as a framework-style extension.

  • ✅ 50+ hooks (filters + actions)
  • ✅ Fully customizable columns
  • ✅ Template override system
  • ✅ Extensible query layer
  • ✅ Clean separation of logic and UI

You can literally customize every part of the table — from query → UI → cart behavior.


🛠️ Example: Add a Custom Column

You can extend the table like this:

add_filter( 'wcpt_table_columns', function( $columns ) {
$columns['sku'] = [
'label' => 'SKU',
'sortable' => true,
];
return $columns;
});add_action( 'wcpt_render_column_sku', function( $product ) {
echo $product->get_sku();
});

This level of extensibility is intentional — I wanted this to work well in real-world enterprise setups, not just demos.


⚡ Quick Usage

Just drop the shortcode:

[wc_product_table]

Or customize it:

[wc_product_table per_page="25" category="clothing"]

Simple, flexible, and production-ready.


🎯 Where This Works Best

This plugin is ideal for:

  • Wholesale stores
  • B2B dashboards
  • LMS product/course listings
  • Bulk ordering interfaces
  • High SKU catalogs

🏗️ My Approach While Building This

Since I work as a WordPress architect, I built this with a few strong principles:

  • Performance first
  • Extensibility over shortcuts
  • Clean architecture
  • Real-world usability

This is not a “just make it work” plugin — it’s designed to scale.


🔓 Open Source

The plugin is fully open source.

👉 https://github.com/vishalkakadiya/wc-product-table/

If you want to:

  • Extend it
  • Use it in your projects
  • Or contribute

Feel free to jump in.


🚀 What’s Next

I’m planning to evolve this further with:

  • Advanced filters
  • Gutenberg block support
  • More UI customization
  • Better performance optimizations

💬 Feedback Welcome

GitHub: https://github.com/vishalkakadiya/wc-product-table

If you try it out, I’d love to hear:

  • What works
  • What breaks
  • What you wish it could do

If you’re building serious WooCommerce solutions, you already know:

👉 UX + performance + flexibility = everything

This plugin is built exactly with that mindset.

Mastering the WordPress Abilities API: Fine-Grained Control for Modern WordPress Development

The WordPress Abilities API provides a standardized way to register and discover distinct units of functionality within a WordPress site. These units, called “Abilities”, represent specific actions or capabilities that components can perform, with clearly defined inputs, outputs, and permissions.

It acts as a central registry, making it easier for different parts of WordPress, third-party plugins, themes, and external systems (like AI agents) to understand and interact with the capabilities available on a specific site.

Core Concepts

  • Ability: A distinct piece of functionality with a unique name following the namespace/ability-name pattern. Each ability has a human-readable name and description, input/output definitions (using JSON Schema), a category assignment, optional permissions, and an associated callback function for execution. Each registered Ability is an instance of the WP_Ability class.
  • Category: A way to organize related abilities. Each ability must belong to exactly one category. Categories have a slug, label, and description. Each registered category is an instance of the WP_Ability_Category class.
  • Registry: A central, singleton object (WP_Abilities_Registry) that holds all registered abilities. It provides methods for registering, unregistering, finding, and querying abilities. Similarly, WP_Abilities_Category_Registry manages all registered categories.
  • Callback: The PHP function or method executed when an ability is called via WP_Ability::execute().
  • Schema: JSON Schema definitions for an ability’s expected input (input_schema) and its returned output (output_schema). This allows for validation and helps agents understand how to use the ability.
  • Permission Callback: An optional function that determines if the current user can execute a specific ability.
  • Namespace: The first part of an ability name (before the slash), typically matching the plugin or component name that registers the ability.

Important Resources

  • https://developer.wordpress.org/apis/abilities-api/
  • https://developer.wordpress.org/news/2025/11/introducing-the-wordpress-abilities-api/

What is the difference between select(‘core’) vs apiFetch, and when to use which?

This is a very important architectural decision in Gutenberg — and honestly, many developers misuse both.

Let’s break it down in a practical, real-world way so you can decide like a tech lead 👇


🧠 select('core') vs apiFetch — What’s the Difference?

Featureselect('core')apiFetch
Data SourceWP Data Store (core-data)Direct REST API
Reactivity✅ Reactive (auto re-render)❌ Not reactive
Caching✅ Built-in caching❌ No caching
Performance✅ Optimized⚠️ Depends on usage
Ease of Use✅ Cleaner in blocks⚠️ More manual
Control⚠️ Limited✅ Full control

✅ When You Should Use select('core') (Recommended Most of the Time)

👉 Use this inside Gutenberg blocks/UI

✔ Best for:

  • Fetching posts, pages, users
  • Displaying dynamic data in UI
  • When you want automatic updates
  • When performance matters

Example

const posts = useSelect((select) => {
return select('core').getEntityRecords('postType', 'post');
}, []);

💡 Why it’s better

  • Uses centralized state (Redux-like store)
  • Avoids duplicate API calls
  • Auto re-renders UI when data changes
  • Works seamlessly with Gutenberg

⚠️ When apiFetch is Better

👉 Use this when you need full control or custom behavior

✔ Best for:

  • Custom REST API endpoints
  • POST / PUT / DELETE requests
  • Non-Gutenberg environments
  • One-time operations (form submit, migration scripts, etc.)

Example

import apiFetch from '@wordpress/api-fetch';const fetchData = async () => {
const posts = await apiFetch({
path: '/wp/v2/posts?per_page=5',
}); console.log(posts);
};

🚨 Common Mistake (Very Important)

❌ Using apiFetch inside React render logic:

// ❌ BAD PRACTICE
const posts = await apiFetch({ path: '/wp/v2/posts' });

👉 This causes:

  • Multiple API calls
  • No caching
  • Performance issues

⚡ Real-World Decision Rule (Use This)

👉 Ask yourself:

1. Is this UI data inside Gutenberg?

👉 ✅ Use select('core')


2. Is this a custom API or action (POST/UPDATE)?

👉 ✅ Use apiFetch


3. Do I need caching + reactivity?

👉 ✅ Use select('core')


4. Do I need full control over request?

👉 ✅ Use apiFetch


🏆 Best Practice (Senior-Level Pattern)

👉 Combine both when needed:

  • Use select('core') → for reading data
  • Use apiFetch → for writing/updating data

Example Pattern

// Read (cached + reactive)
const posts = useSelect((select) =>
select('core').getEntityRecords('postType', 'post')
);// Write (manual control)
const createPost = async () => {
await apiFetch({
path: '/wp/v2/posts',
method: 'POST',
data: { title: 'New Post' },
});
};

🧩 Bonus Insight (Interview-Level)

👉 select('core') internally:

  • Uses @wordpress/core-data
  • Wraps REST API calls
  • Adds caching + resolution tracking
  • Prevents duplicate requests

🏁 Final Verdict

👉 If you’re building Gutenberg blocks:

Use select('core') 80–90% of the time

👉 Use apiFetch only when:

  • You need custom logic
  • Or you’re mutating data

A Practical Guide to Fetching Custom Post Types and Taxonomies Using select(‘core’) in Gutenberg

✅ 1. Fetch Custom Post Type (product)

Just replace 'post' with 'product':

const { products, isLoading } = useSelect((select) => {
	const query = {
		per_page: 5,
		_fields: ['id', 'title'],
	};	return {
		products: select('core').getEntityRecords('postType', 'product', query),
		isLoading: !select('core').hasFinishedResolution(
			'getEntityRecords',
			['postType', 'product', query]
		),
	};
}, []);





✅ 2. Fetch Taxonomy (product_cat)

For taxonomies, entity type changes from 'postType''taxonomy'

const { categories, isCatLoading } = useSelect((select) => {
	const query = {
		per_page: 10,
		_fields: ['id', 'name'],
	};	return {
		categories: select('core').getEntityRecords('taxonomy', 'product_cat', query),
		isCatLoading: !select('core').hasFinishedResolution(
			'getEntityRecords',
			['taxonomy', 'product_cat', query]
		),
	};
}, []);





🚀 3. Fetch BOTH (Best Practice – Single useSelect)

👉 This is what you should do in production (avoids multiple subscriptions)

const { products, categories, isLoading } = useSelect((select) => {
	const productQuery = {
		per_page: 5,
		_fields: ['id', 'title'],
	};	const categoryQuery = {
		per_page: 10,
		_fields: ['id', 'name'],
	};	const core = select('core');	return {
		products: core.getEntityRecords('postType', 'product', productQuery),
		categories: core.getEntityRecords('taxonomy', 'product_cat', categoryQuery),		isLoading:
			!core.hasFinishedResolution('getEntityRecords', ['postType', 'product', productQuery]) ||
			!core.hasFinishedResolution('getEntityRecords', ['taxonomy', 'product_cat', categoryQuery]),
	};
}, []);

🔗 4. Filter Products by Category (Very Useful)

If you want products by category, pass taxonomy query:

const query = {
per_page: 5,
product_cat: 12, // category ID
};

👉 Full example:

products: core.getEntityRecords('postType', 'product', {
	per_page: 5,
	product_cat: 12,
})





⚠️ Important Notes

1. CPT must be exposed to REST API

When registering CPT:

'show_in_rest' => true,

2. Taxonomy must also support REST

'show_in_rest' => true,

3. _fields optimization

Be careful:

_fields: ['id', 'title']
  • Works for posts
  • For taxonomy → use name, not title

🧠 Key Takeaways

  • CPT → 'postType', 'product'
  • Taxonomy → 'taxonomy', 'product_cat'
  • Combine in single useSelect for performance
  • Use hasFinishedResolution for loading state
  • Pass taxonomy slug in query to filter posts