Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Wp Mock Laravel Package

10up/wp_mock

wp_mock is a Mockery-powered library for unit testing WordPress plugins and themes. It lets you mock core functions, hooks, and globals so tests run fast and isolated, without a WordPress install.

Deep Wiki
Context7

Getting Started

Install and wire it up (10 minutes).

composer require --dev 10up/wp_mock mockery/mockery phpunit/phpunit

WP_Mock rides on Mockery for its assertion engine, so both come along. Then create a bootstrap file and point PHPUnit at it:

<?php
// tests/bootstrap.php
require_once __DIR__ . '/../vendor/autoload.php';

WP_Mock::bootstrap();          // defines the mocking layer + WP constants shim
<!-- phpunit.xml.dist -->
<phpunit bootstrap="tests/bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="unit"><directory>tests/Unit</directory></testsuite>
    </testsuites>
</phpunit>

Finally, give every test a common base class so setUp/tearDown are never forgotten — this is the single biggest source of "why is my mock not intercepting anything?" confusion:

abstract class WPTestCase extends \PHPUnit\Framework\TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        WP_Mock::setUp();      // MUST come before the code under test runs
    }

    protected function tearDown(): void
    {
        WP_Mock::tearDown();   // MUST run, even on failure — this is what verifies expectations
        parent::tearDown();
    }
}

Where to look first: the README's four API sections — Mocking WordPress Functions, Filters, Actions, Constants. Everything else in the package is plumbing. If you want to see the full public surface, read vendor/10up/wp_mock/php/WP_Mock.php top to bottom; it is one static facade and takes about five minutes.

Your first real use case. A plugin class that reads an option and registers a content filter — the classic 80% case:

class Banner_Test extends WPTestCase
{
    public function test_registers_and_renders(): void
    {
        WP_Mock::userFunction('get_option', [
            'args'   => ['banner_text'],
            'return' => 'Hello',
            'times'  => 1,
        ]);
        WP_Mock::userFunction('esc_html', ['return' => 'Hello']);

        $banner = new Banner();
        $this->assertSame('<p>Hello</p>', $banner->render());
    }

    public function test_hooks_the_content_filter(): void
    {
        WP_Mock::expectFilterAdded('the_content', [new Banner(), 'append'], 10, 1);

        (new Banner())->register();
    }
}

Note the split: one test exercises behaviour, one asserts wiring. That separation is the whole point of the package.

Implementation Patterns

Array config vs. chained Mockery expectations. userFunction() returns a Mockery expectation, so both styles are legal and they mix:

WP_Mock::userFunction('wp_remote_get', ['args' => ['https://api.test/x']])
    ->once()
    ->andReturn(['response' => ['code' => 200], 'body' => '{"ok":true}']);

The array keys are times, args, return, and strict. The default times is 1, which is important (see Gotchas).

passthruFunction() for the noise layer. Escaping and i18n functions usually shouldn't be asserted — they just need to not blow up. passthruFunction accepts any number of calls and returns the first argument, which is exactly the identity behaviour you want:

WP_Mock::passthruFunction('esc_html');
WP_Mock::passthruFunction('esc_attr');
WP_Mock::userFunction('__', ['return' => fn($s) => $s]);  // callables are invoked

Put these in your base class setUp() and never think about them again.

Filters: two distinct jobs. Use onFilter() when your code consumes a filter and you need to control the value returned; use expectFilter()/expectFilterAdded() when your code publishes or registers one.

// Control the return value of apply_filters('price', 100, $product)
WP_Mock::onFilter('price')->with(100, Mockery::type(Product::class))->reply(120);

// Transform it dynamically
WP_Mock::onFilter('price')
    ->with(Mockery::any())
    ->replyUsing(fn($price) => $price * 2);

// Assert that your code applied a filter with a specific value
WP_Mock::expectFilter('my_plugin_settings', ['a' => 1]);

// Assert registration
WP_Mock::expectFilterAdded('the_content', [$plugin, 'filter_content'], 20, 1);
WP_Mock::expectFilterNotAdded('the_content', Mockery::any()); // negative case

Actions. Same split: expectAction() asserts do_action() was fired with args, expectActionAdded() asserts add_action() registration.

WP_Mock::expectAction('order_completed', 42);
WP_Mock::expectActionAdded('init', [$plugin, 'boot'], 10, 0);

Test the callback directly; assert registration separately. WP_Mock records hooks, it does not execute them. So don't try to drive your filter through apply_filters — instantiate the class and call the method:

public function test_append_transforms_content(): void
{
    $banner = new Banner();
    $this->assertSame("body<p>Hi</p>", $banner->append("body"));
}

That one decision removes more pain than any other.

Globals aren't provided — you wire them. $wpdb and $current_user are plain globals, so mock them yourself:

protected function setUp(): void
{
    parent::setUp();
    $GLOBALS['wpdb'] = Mockery::mock('wpdb');
    $GLOBALS['wpdb']->prefix = 'wp_';
    $GLOBALS['wpdb']->shouldReceive('get_var')->andReturn('3');
}

Class dependencies (WP_Query, WP_Error, WP_REST_Request). WordPress classes generally aren't loaded, so a bare Mockery::mock('WP_Query') fatals. Use the alias: prefix and define them in a shared helper:

$query = Mockery::mock('alias:WP_Query');
$query->shouldReceive('have_posts')->andReturn(true);

Reusable stub bundles. Any function that returns an array (get_option for a settings array, wp_parse_args, get_post_meta) will produce silent foreach-on-null errors if you forget it. Wrap them in helper methods on your base class (stubOptions([...]), stubPostMeta([...])) so intent is one line per test.

Gotchas and Tips

  • Never load WordPress core in the same PHPUnit process. WP_Mock intercepts by defining global functions; if WP core already defined them, you get fatal redeclare errors. Unit tests (WP_Mock) and integration tests (the WP test suite) must be separate suites with separate bootstraps.
  • times defaults to 1. Forget it and a function called twice blows up with an InvalidCountException; a function called zero times also blows up. Use 'times' => 0 to assert not called, or chain ->zeroOrMoreTimes() when the count is irrelevant.
  • Argument matching is exact. ['args' => ['my_option']] only matches a single-argument call with that literal. Real WP code passes defaults, so use Mockery::any(), Mockery::type('string'), or Mockery::on(fn($v) => ...) liberally. NoMatchingExpectationException shows expected vs. actual — read it, it names the culprit.
  • onFilter()->with() includes the filtered value as the first parameter. If your code calls apply_filters('x', $value, $extra) but you only specify with($value), it won't match. Pass every argument, or with(Mockery::any(), Mockery::any()).
  • Hooks are recorded, never run. expectActionAdded() inspects a static registry that WP_Mock::setUp() resets per test — so set expectations before the code registers, and never expect a registered callback to fire.
  • function_exists() lies until you mock. The mocking layer defines functions lazily, so code that guards on function_exists('wp_remote_get') behaves differently from production. Be aware when testing feature-detection branches.
  • Your own plugin's functions and all class methods are off-limits. WP_Mock mocks global functions only. Mock objects/classes with Mockery; keep business logic in classes so it's testable.
  • WP_Mock::tearDown() must always run — it's what calls Mockery's verification. Skipping it on early failure hides unmet expectations; putting it after parent::tearDown() can also break things. Keep it first.
  • Pin your PHPUnit major. WP_Mock's composer constraint historically tops out at PHPUnit 9. On PHPUnit 10/11, run composer why-not 10up/wp_mock before debugging phantom failures — it's usually a version conflict, not your test.
  • wpFunction() / wpPassthruFunction() are deprecated aliases for userFunction() / passthruFunction(). New code should never use them.
  • Constants come from define() in the bootstrap, not from WP_Mock. Define ABSPATH, MY_PLUGIN_VERSION, etc. before WP_Mock::bootstrap() and any code with defined() guards will behave.
  • Debugging trick: when a mock "returns nothing", add ->andReturnUsing(function (...$args) { fwrite(STDERR, print_r($args, true)); return null; }) to see the actual call signature. Faster than reading Mockery stack traces.
  • Extension point worth using: everything is WP_Mock:: static, so layer your own facade (PluginTestCase::stubSettings()) over it. That gives you one place to change when you upgrade and one place to audit which WP surfaces your suite depends on.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource