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

Mastering select(‘core’) in WordPress Gutenberg (With Simple Examples)

When working with Gutenberg blocks, accessing WordPress data efficiently is essential. One of the most commonly used tools for this is select('core').

In this guide, we’ll break it down in a simple and practical way, with examples you can use immediately.


📌 What is select('core')?

In Gutenberg:

  • select() is used to read data from a data store
  • 'core' refers to the WordPress core data store
  • It allows you to fetch:
    • Posts
    • Pages
    • Users
    • Taxonomies
    • Media

👉 In simple terms:

select('core') lets you fetch data from WordPress (via REST API) inside your block.


⚠️ Basic Example (Not Recommended for UI)

import { select } from '@wordpress/data';

const posts = select('core').getEntityRecords('postType', 'post');




❗ Why this is not ideal?

  • It is not reactive
  • UI will not update automatically
  • Should not be used inside React render logic

✅ Correct Way: Using useSelect

The recommended approach in Gutenberg is using the useSelect hook.


🎯 Example: Fetch Posts

import { useSelect } from '@wordpress/data';

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

💾 Storing Data in a Variable & Using It

Here’s a simple working example:

import { useSelect } from '@wordpress/data';

export default function MyComponent() {

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

    if (!posts) {
        return 'Loading...';
    }

    return (
        <ul>
            {posts.map((post) => (
                <li key={post.id}>
                    {post.title.rendered}
                </li>
            ))}
        </ul>
    );
}

🔍 What’s Happening Here?

  • useSelect() subscribes to the data store
  • posts variable stores fetched data
  • Initially, it returns null → then updates with real data
  • Component re-renders automatically when data is available

⚡ Using Query Parameters (Best Practice)

To improve performance, always limit the data you fetch:

const posts = useSelect((select) => {
    return select('core').getEntityRecords('postType', 'post', {
        per_page: 5,
        _fields: ['id', 'title'],
    });
}, []);

✅ Benefits:

  • Faster API response
  • Less memory usage
  • Cleaner data

🚀 Advanced Pattern (Loading State Handling)

const { posts, isLoading } = useSelect((select) => {
    const query = { per_page: 5 };

    return {
        posts: select('core').getEntityRecords('postType', 'post', query),
        isLoading: !select('core').hasFinishedResolution(
            'getEntityRecords',
            ['postType', 'post', query]
        ),
    };
}, []);

🧠 Why this is important?

  • Helps handle loading state properly
  • Prevents UI flickering
  • Useful for production-grade blocks

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