Giter VIP home page Giter VIP logo

wp_mock's Introduction

WP_Mock

WP_Mock is an API mocking framework, built and maintained by 10up and GoDaddy for the purpose of making it possible to properly unit test within a WordPress project.

Support Level PHP 7.4+ Coverage Status Packagist BSD-3-Clause License

Installation

Install WP_Mock as a dev-dependency using Composer:

composer require --dev 10up/wp_mock

Documentation

Learn more about how to configure and how to use WP_Mock by reading the WP_Mock documentation.

Changelog

A complete listing of all notable changes is documented in the Changelog.

Support Level

Active: 10up and GoDaddy are actively working on this, and we expect to continue work for the foreseeable future, including testing with the most recent version of WordPress and PHP. Bug reports, feature requests, questions, and pull requests are welcome.

Contributing

Please read our Code of Conduct for details on our code of conduct and our Contributing Guidelines for details on the process for submitting pull requests.

Supporters

WP_Mock is supported by 10up and GoDaddy. GitBook kindly offers free hosting for WP_Mock documentation.

A special thanks to all WP_Mock contributors.

Like what you see?

wp_mock's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wp_mock's Issues

Fatal Error from sample code

Using the example provided in the readme you'll receive a fatal error.

$ phpunit --bootstrap tests/bootstrap.php tests/MyTestClass.php 
PHPUnit 3.7.31 by Sebastian Bergmann.

PHP Fatal error:  Call to undefined function setup_postdata() 

Method add_action() should be called exactly 1 times but called 0 times.

Hey folks. Thank you for this great framework!

I'm having an issue with the times argument to WP_Mock::wpFunction(). Seems like a pretty basic use-case so I'm not totally certain I didn't miss something. Here's the minimal repro code:

<?php

require_once 'vendor/autoload.php';

use PHPUnit\Framework\TestCase;

trait HasCustomAdminFilters {
  public function add_admin_filter( $name, $options, $postType, callable $queryModifier ) {
    add_action('pre_get_posts', function(\WP_Query $query) use($name, $postType, $queryModifier) {
      // do stuff
    });
  }
}

class HasCustomAdminFiltersTest extends TestCase {
  public function setUp() {
    WP_Mock::setUp();
  }

  public function tearDown() {
    WP_Mock::tearDown();
  }

  public function testAddAdminFilter() {
    WP_Mock::wpFunction('add_action', [
      'times' => 1,
    ]);

    // register the action handlers
    $this->getObjectForTrait('HasCustomAdminFilters')->add_admin_filter(
      'custom_filter',
      ['dropdown' => 'options'],
      'my_post_type',
      function() {}
    );
  }
}

Running this test outputs this error:

Mockery\Exception\InvalidCountException: Method add_action() from Mockery_0__wp_api should be called
 exactly 1 times but called 0 times.

/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery/CountValidator/Exact.php:37
/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery/Expectation.php:297
/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery/Container.php:297
/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery/Container.php:282
/vagrant/projects/groot/vendor/mockery/mockery/library/Mockery.php:142
/vagrant/projects/groot/vendor/10up/wp_mock/WP_Mock.php:112
/vagrant/projects/groot/test/HasCustomAdminFiltersTest.php:30

I'm using WP_Mock at dev-master (updated just now) and PHPUnit 5.5.4 on PHP 5.6.

Any ideas?

Add a strict mode

This will be off by default.

Currently, if a function is defined by WP_Mock and has no expectations defined, it will run as a no-op function with a return value of null. We need a strict mode that will cause failure if a function is used without an explicit expectation. This will also cause add_action and add_filter to fail without an explicit expectation (currently the do not fail if unexpected).

Testing Singletons

I use the singleton pattern for most of the classes in my plugin.

trait Singleton {

	private static $instances = array();

	/**
	 * Provides access to a single instance of a module using the singleton pattern
	 *
	 * @return object
	 */
	public static function get_instance() {
		$module = get_called_class();

		if ( ! isset( self::$instances[ $module ] ) ) {
			self::$instances[ $module ] = new $module();
		}

		return self::$instances[ $module ];
	}

}

The classes using this trait also have a register_hook_callbacks method that's called from the constructor. All of the singletons are instantiated in this fashion:

add_action( 'plugins_loaded', array( Foo::class, 'get_instance' ) );

As soon as plugins are loaded, the instance is set up, the constructor is called, and the hooks are registered. I can't seem to get this to play nice with WP_Mock. Here's what I thought would work:

/** @test */
public function it_adds_filters()
{
	$instance = Cron::get_instance();
	WP_Mock::expectActionAdded('wp', [$instance, 'maybe_schedule_cleanup']);
}

I know the hook is registered, but the test fails. I've also tried explicitly calling the hook registration after setting the expectation.

/** @test */
public function it_adds_filters()
{
	$instance = Cron::get_instance();
	WP_Mock::expectActionAdded('wp', [$instance, 'maybe_schedule_cleanup']);
	$instance->register_hook_callbacks();
}

Any suggestions?

Thanks.

Restructure Directories

In anticipation to writing tests for our test framework, I'd like to propose that we move our code into a src directory. Obviously we'd need to update the autoload configuration in composer.json. Any other unintended consequences anybody can think of?

What is a Unit Test?

WP_Mock is an API mocking framework, built and maintained by 10up for the purpose of making it possible to properly unit test within WordPress.

Sorry to bother you. But I've been doing some research while building out unit tests for a WordPress plugin I'm building and discovered a misnomer in the vernacular being used in the community with regard to what actually constitutes unit testing.

My understand is that, in using WP_Mock or similar, while creating WordPress plugins what was is doing is actually creating an "integration test" and not a "unit test".

To help justify my rationale I've set-up an application capable of both unit tests and integration testing. Here's what an example integration test looks like:

/**
 * Sample test case.
 */
class SampleTest extends WP_UnitTestCase {

	/**
	 * A single example test.
	 */
	function test_sample() {
		// Replace this with some actual testing code.
		$this->assertTrue( true );
	}
}

It's an integration test because it requires an understanding of and dependence on WordPress internals in order to execute.

Whereas here's what an actual unit test looks like:

namespace hyperdrive\spec;
use function hyperdrive\enter_hyperspace;

describe('hyperdrive', function () {
  describe('enter_hyperspace()', function () {
    it('echos empty script given empty string', function () {
      $closure = function () { enter_hyperspace(''); };
      expect($closure)->toEcho('<script></script>');
    });

    it('echos string in script given string', function () {
      $closure = function () { enter_hyperspace('Danger, Will Robinson!'); };
      expect($closure)->toEcho('<script>Danger, Will Robinson!</script>');
    });

    it('does not echo unexpected string', function () {
      $closure = function () { enter_hyperspace('Danger, Will Robinson!'); };
      expect($closure)->not->toEcho('<script>Warning! Warning!</script>');
    });
  });
});

Above snippet copied from hyperdrive.spec.php and licensed for reuse under AGPL.

Notice there is no reliance on WordPress in order to operate; code is operating on PHP source capable of being run in any system and independent WordPress internals.

Which begs the question, what is actually considered a unit test when working with WordPress, and should the community consider adjusting vernacular to help prevent misunderstanding and starting with adjusting the blockquote pulled from the WP_Mock README file copied above?

WPMock::wpFunction appears incompatible with allowMockingNonExistentMethods

I'm having difficulty with WP_Mock when I'm disabling mocking nonexistent methods:

Mockery has a setting that prevents you creating imaginary tests which mock methods which don't exist:

\Mockery::getConfiguration()->allowMockingNonExistentMethods(false);

When I enable this and use e.g.

\WP_Mock::wpFunction('get_posts', ...

Mockery complains because the WP methods I'm mocking don't exist on the objects in question (presumably because they're global functions). Here's the error:

Mockery's configuration currently forbids mocking the method get_posts as it does not exist on the class or object being mocked

I'm working around this by selectively disabling this setting in tests which use WP_Mock (I'm using peridot for tests):

beforeEach(function() {
    \WP_Mock::setUp();
    \Mockery::getConfiguration()->allowMockingNonExistentMethods(true);
});

afterEach(function() {
    \WP_Mock::tearDown();
    \Mockery::getConfiguration()->allowMockingNonExistentMethods(false);
});

Does anyone know of a better approach? Or is the solution to patch WP_Mock such that mockery ignores these methods when checking for mocking nonexistent methods?

Add an alias method

Something like:

WP_Mock::alias( 'wp_hash', 'md5' );
WP_Mock::alias( 'trailingslashit', function( $it ) { return rtrim( $it, '/' ) . '/'; } );

PHPUnit 6 Compatibility

This is related to #84. We will eventually need to support PHPUnit 6. For now, we're just requiring a version >=4.4 and <6.0. Ideally we can support PHPUnit 6.0 and lower, although I'm not sure how, since a lot of the codebase has been restructured (e.g. PHPUnit_Framework_TestCase has been renamed to PHPUnit\Framework\TestCase).

expectActionAdded not counting as an assertion when running test

I might be a little confused here, but when I run \WP_Mock::expectActionAdded, I get an all OK from the test, but the expectActionAdded calls don't seem to count towards the assertion count. Am I doing something wrong or does that mean they're passing and they would return a PHP error if they weren't?

\WP_Mock::expectActionAdded( 'admin_bar_menu', array( $this->Site_Lockdown, 'page_lockdown_display' ), 500 );
\WP_Mock::expectActionAdded( 'admin_init', array( $this->Site_Lockdown, 'set_lockdown_option' ), 105 );
\WP_Mock::expectActionAdded( 'init', array( $this->Site_Lockdown, 'lockdown_this_thing' ), 105 );
\WP_Mock::expectActionAdded( 'init', array( $this->Site_Lockdown, 'lockdown_actions' ), 5 );
\WP_Mock::expectActionAdded( 'admin_init', array( $this->Site_Lockdown, 'lockdown_actions' ), 5 );

$this->Site_Lockdown->hooks();

Require as development dependency

Only a minor cosmetic change in the installation instructions: this package is clearly a dependency only required during development at should be added as such. Who wants to run unit tests on a production environment? :)

The required switch for this is --dev (https://getcomposer.org/doc/03-cli.md#require):

composer require --dev 10up/wp_mock:dev-master

Question about actions and filters

How use expectActionAdded and expectFilterAdded with class method. Example

class SupportWebP
{
    public function __construct()
    {
        add_filter('upload_mimes', [$this, 'supportWebP']);
    }

    public function supportWebP($extensions)
    {
        $extensions['webp'] = 'image/webp';

        return $extensions;
    }
}

How a can test upload_mimes filter?

Change in Patchwork dependency throwing warnings

I was able to track this down to the dependency of antecedent/patchwork v1.4.1. They recently updated, and in the process removed their autoloader from composer. This appears to be causing some issues with the require statement in WP_Mock which includes Patchwork within the bootstrap function.

watson$ ./vendor/bin/phpunit
Warning: Please import Patchwork from a point in your code where no user-defined function, class or trait is yet defined. composerrequire1d885e985f1407f27ab4585581d59bad() and possibly others currently violate this. in /Users/user/Sites/vagrant-local/www/project/htdocs/wp-content/themes/theme-name/vendor/antecedent/patchwork/Patchwork.php on line 93
PHPUnit 4.8.23 by Sebastian Bergmann and contributors.

.....

Time: 545 ms, Memory: 12.50Mb

OK (5 tests, 8 assertions)

Using wp_mocks together with WordPress core Unit tests

I know that wp_mock and WordPress core Unit tests work differently and it may not even be possible to have them run together.

I use both and like both. But there are certain tests for which I would prefer wp_mock and certain tests for which I would prefer WordPress core Unit tests. But unfortunately right now there is no way to configure both of them for a single plugin. You have to pick one and stick with it even if you can group all the 'mock' tests using a separate testsuite or phpunit group and invoke the separately from phpunit.

What I am suggesting is to have a way to setup both of them together for a plugin and invoke the appropriate framework and their tests using either phpunit's group or testsuite feature.

I know that there was some recent discussion about it and I am interested in knowing everyone's thoughts on it.

Pick a License, Any License

https://blog.codinghorror.com/pick-a-license-any-license/

Because I did not explicitly indicate a license, I declared an implicit copyright without explaining how others could use my code. Since the code is unlicensed, I could theoretically assert copyright at any time and demand that people stop using my code. Experienced developers won't touch unlicensed code because they have no legal right to use it.

antecedent/patchwork version update

I believe this relates to #56 as well. I cannot install this package with the latest version of lucatume/wp-browser because of a dependancy mis-match with antecedent/patchwork

wp_mock uses antecedent/patchwork version 1.x
wp-browser now uses antecedent/patchwork version 2.x

Any plans to update the antecedent/patchwork requirements of this project?

Throw Exception when handle_function fails

When a function is mocked using Functions::create_function(), two things are created:

  1. The actual function is defined using eval()
  2. The function handler is bound using Handler:register_handler()

When the test is complete and Functions::flush() is called, it removes the
handler, but nothing removes the function itself, which will continue to call
Handler::handle_function() in later tests, except that this will now be a
noop.

This can create problems when, for example, one test mocks a function to allow a
certain series of function calls, and then a later test also uses that series of
function calls, but does not mock the function. In this case, if the first test
is run before the second test, they will both pass, but if the second test is
run first (or in isolation), it will fail because it calls an undefined
function. Test Mocks should not cause tests to fail if run in different order.

While it may be impossible to replicate a call to an undefined function, we can
simulate it by throwing an Exception. I'd suggest a change that causes
Handler::handle_function() to throw an Exception if it doesn't find a matching
handler.

Example:

<?php

class SomeClass {
  public function do_something( $times ) {
    $user = wp_get_current_user();
    //...
    return true;
  }
}

class MyTestClass extends PHPUnit_Framework_TestCase {
  public function setUp() {
    \WP_Mock::setUp();
    $this->something = new SomeClass();
  }

  public function tearDown() {
    \WP_Mock::tearDown();
  }

  public function test_do_something() {
    \WP_Mock::wpFunction( 'wp_get_current_user' );
    $this->assertTrue( $this->something->do_something( 1 ) );
  }

  public function test_do_something_again() {
    $this->assertTrue( $this->something->do_something( 2 ) ); // This should cause a fatal error, but it does not if run after the previous test
  }
}

Trouble getting a simple check to work

Okay so I dunno if I'm just missing something here. I've trimmed everything down to the very very basics. I have WP_Mock setup, and I have a few working unit tests already. I wanted to check if an action was being done do_action in a function although with anything I do, I get this error after the test:

Mockery\Exception\InvalidCountException: Method intercepted() from Mockery_0__intercept should be called
 at least 1 times but called 0 times.
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery/CountValidator/AtLeast.php:48
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery/Expectation.php:271
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery/Container.php:297
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery/Container.php:282
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/mockery/mockery/library/Mockery.php:142
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/vendor/10up/wp_mock/WP_Mock.php:112
/vagrant/www/wordpress-default/wp-content/plugins/sumo-search/tests/test-titan-framework-checker.php:12

This is my back to basics test file:

<?php

class TitanFrameworkCheckerTest extends WP_UnitTestCase {

    public function setUp() {
        parent::setUp();
        \WP_Mock::setUp();
    }

    public function tearDown() {
        parent::tearDown();
        \WP_Mock::tearDown();
    }

    function test_perform_check() {
        $o = new TitanFrameworkChecker();

        \WP_Mock::expectAction( 'my_action' );

        $result = $o->perform_check();

    }

}

Now this is the code being checked:

class TitanFrameworkChecker {

    public function perform_check() {
        do_action( 'my_action' );
    }

}

That test code above gives out the same error for me. I can't get this simple one to work. I've also tried with other functions like expectFilterAdded.

I would appreciate any help on this.

WP_Mock Always Returns 0

When executing the first test of Using WP_Mock, with its respective function my_permalink_function. And also other test, WP_Mock always returns false.

mocking translation functions

I get a PHP Fatal error PHP Fatal error: Call to undefined function BBL\Classes\__() when running my tests. I've tried mocking WordPress's __() by doing
\WP_Mock::wpPassthruFunction('__',);
and

\WP_Mock::wpFunction( '__', [
            'times' => 1,
            'args' => ['%s is not a valid app-key for %s shortcode', 'bbl'],
            'return_args' => 0
        ] );

I get the error both times.

Is the __() function a function I'll need to mock? I just want the first argument returned.

Should lack of wpPassthruFunction generate fatal errors during testing?

It seems to me that at most, there could be warnings or notices generated for things not properly mocked up for passing thru or mocking things like esc_attr / esc_html / etc.

It also seems pretty difficult to rectify these when running tests results in fatals where a function doesn't exist in the namespace, but it's a global WP function.

Should there be a way to enable tests to run as expected, with notices thrown out for things that are not covered?

Drop Support for PHPUnit 3.7.x

I wanted to start a discussion around this idea. The last release on the 3.x branch was 6 months ago. I'm not sure if the branch has received its EOL yet. Will investigate.

Anyway, 4.0 introduced a lot of nice things. I'm primarily interested in the addition of the concept of "risky" tests for #18.

Rename wp* functions without "wp" prefix

e.g. WP_Mock::wpFunction() would be renamed to WP_Mock::function(). Of course, for backwards compatibility, the original names would remain and would simply call the new names internally.

There is no documentation on how to properly bootstrap WP Mock

Hey guys, googled quite a lot but haven't found anything that worked for me, so I came directly to you. It's about the setup.

All I want to do is to test my own functions that are in functions.php.
I have to setup a phpunit.xml and also a bootstrap file so I can automate the tests.

My bootstrap file:
require_once dirname( __FILE__ ) . '/../../../../vendor/autoload.php';
require_once dirname( __FILE__ ) . '/../functions.php';

And my phpunit.xml calls it.

The issue:
Before using bootstrap.php and phpunit.xml my test was working fine, but with all that require and stuff.
After this setup, now I'm getting:
Fatal error: Call to undefined function add_action()

Tried a few things already, like \WP_Mock::expectAction but not successfully. Any advice?
I think my setup is not complicated so it shouldn't be that hard.

Thank you!

Issue on testing do_action when is called with some arguments

Sincerely I am very surprised to find this bug and found nothing on existing issues: I think this problem should be so common that seems impossible no one has reported it before.

I still have the doubt the problem it's.. me.

Problem

However, I tried to report things as better as possible and to give all the info to reproduce the bug.

When a method to be tested call do_action passing some arguments, like

do_action( 'custom_hook', 'foo', 'bar' )

currently, is impossible for tests to add expectations using expectAction, e .g.

\WP_Mock::expectAction( 'custom_hook' );

Because they fail.

Here, in this gist there is the I used, and can be used to reproduce the bug.

I've used just 2 files: a class to be tested an a test class, keeping things as simple as possible, but the problem remains. In a comment in the Gist there is also a screenshot.

I also added in the Gist the composer.json I used and, really, those 3 are all the file I used (aside vendor folder, of course).

Solution

However, I think I solved the issue by editing a couple of lines in WP_Mock/Action.php.

This compare shows the code I used. It seems to work, and to not affect anything else, but I don't know the repo so well, so I can miss something.

Which branch should be used?

The repository defaults to showing dev branch and it looks like that is the branch with active development. master hasn't been updated in a few years. Which branch do you recommend we install?

Can't use composer to install wp_mock

I'm trying to install wp_mock with composer, but it's telling me that there's a conflict with phpunit 6.

> /Applications/MAMP/bin/php/php7.0.0/bin/php /usr/local/bin/composer require --dev 10up/wp_mock:dev-dev
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - 10up/wp_mock dev-dev conflicts with phpunit/phpunit[6.1.x-dev].
    - 10up/wp_mock dev-dev conflicts with phpunit/phpunit[6.1.x-dev].
    - 10up/wp_mock dev-dev conflicts with phpunit/phpunit[6.1.x-dev].
    - 10up/wp_mock dev-dev conflicts with phpunit/phpunit[6.1.x-dev].
    - Installation request for 10up/wp_mock dev-dev -> satisfiable by 10up/wp_mock[dev-dev].
    - Installation request for phpunit/phpunit (locked at 6.1.x-dev) -> satisfiable by phpunit/phpunit[6.1.x-dev].


Installation failed, reverting ./composer.json to its original content.

Process finished with exit code 2 at 14:03:04.
Execution time: 4,563 ms.

Any advice welcome on this...

Expected action is done but not recognized by wp_mock

The WP_Mock::expectAction does not seem to work well or I misunderstood it. The setUp and tearDown methods use the WP_Mock::setUp() and WP_Mock::tearDown() as well. And then this test:

public function testItStoresPostBaseAsOption() {
    $_POST['comfort_permalink_blog_base'] = uniqid();

    \WP_Mock::expectAction( 'update_option' );

    comfort_admin_options_permalink();   // within this an update_option is done

    unset( $_POST['comfort_permalink_blog_base'] );
}

Debugging the code I can say that the update_option action is triggered. WP_Mock throws this error:

1) PHPUnit\Comfort\Admin\Options::testItStoresPostBaseAsOption
Mockery\Exception\InvalidCountException: Method intercepted() from Mockery_0__intercept should be called
 at least 1 times but called 0 times.

Why does wp_mock tell me that when the action is triggered?
I am using version 0.1.1.

WP_Mock::expectHookAdded() does not support calling add_action() with a closure

TL;DR: when I call add_action() with a closure callback, expectHookAdded() does not recognize the closure as having been called. Adding a call to $responder->react() inside expectHookAdded() fixes this.

Since the real add_action() is designed to take any callable, I would expect to be able to call expectHookAdded with any valid callable, including a closure.

For example, I am trying to unit test a trait that encapsulates adding an admin filter on the Posts listing screen. Specifically, I'm testing the trait's addAdminFilter() method. I would expect to be able to do something like this:

<?php

require_once 'vendor/autoload.php';

use PHPUnit\Framework\TestCase;

trait HasCustomAdminFilters {
  public function addAdminFilter( $name, $options, $postType, callable $queryModifier ) {
    // allow user to define how they want the admin query modified
    add_action('pre_get_posts', function(\WP_Query $query) use($name, $postType, $queryModifier) {
      // call the user-defined query modifying code
      $queryModifier($query);
    });
  }
}

class HasCustomAdminFiltersTest extends TestCase {
  public function setUp() {
    WP_Mock::setUp();
  }

  public function tearDown() {
    WP_Mock::tearDown();
  }

  public function testAddAdminFilter() {
    $callback = function() {};

    WP_Mock::expectActionAdded('pre_get_posts', WP_Mock\Functions::type('callable'));

    // register the action handlers
    $this->getObjectForTrait('HasCustomAdminFilters')->addAdminFilter(
      'custom_filter',
      ['dropdown' => 'options'],
      'my_post_type',
      $callback
    );
  }
}

This throws:

Mockery\Exception\InvalidCountException: Method intercepted() from Mockery_0__intercept should be called at least 1 times but called 0 times.

Digging into the WP_Mock class, I see that the expectHookAdded() code creates an internal mock object and tells it to be intercepted; then it "performs" the added hook.

This works for the basic use-case of declaring a global function, e.g. foo(), and then calling add_action('pre_get_posts', 'foo'). But in my use-case, I want to encapsulate my callback as a closure, not expose it as a global function. Adding a call to $responder->react() at the end of expectHookAdded() fixes this issue.

I'm not sure if such a change would have other implications. Is it an appropriate solution? Should expectAction() also get the same treatment?

Mockery InvalidCountException when using WP_Mock::expectActionAdded

When I use WP_Mock::expectActionAdded() in my tests it always throws an exception.

Code being tested:

Class se_admin {

    var $version = '6.9';

    function se_admin() {

        // Load language file
        $locale = get_locale();
        if ( !empty($locale) )
            load_textdomain('SearchEverything', SE_ABSPATH .'lang/se-'.$locale.'.mo');


        add_action('admin_head', array(&$this, 'se_options_style'));
        add_action('admin_menu', array(&$this, 'se_add_options_panel'));

    }
}

Test:

<?php
class Admin_Tests extends PHPUnit_Framework_TestCase {

    public $seadmin;

    function setUp() {
        WP_Mock::setUp();
    }

    function tearDown() {
        WP_Mock::tearDown();
    }

    function test_setup() {
        WP_Mock::wpFunction( 'get_locale', array(
            'times' => 1,
            'return' => '',
        ));

        WP_Mock::expectActionAdded( 'admin_head', array( &$this->seadmin, 'se_options_style' ) );
        WP_Mock::expectActionAdded( 'admin_menu', array( &$this->seadmin, 'se_add_options_panel' ) );

        $this->seadmin = new se_admin();
    }
}

Results:

PHPUnit 3.7.29 by Sebastian Bergmann.

Configuration read from /Users/ninnypants/Sites/search-everything-wordpress-plugin/tests/phpunit.xml.dist

E

Time: 47 ms, Memory: 3.75Mb

There was 1 error:

1) Admin_Tests::test_setup
Mockery\Exception\InvalidCountException: Method intercepted() from intercept should be called
 at least 1 times but called 0 times.

/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery/CountValidator/AtLeast.php:47
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery/Expectation.php:236
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery/Container.php:203
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery/Container.php:188
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/mockery/mockery/library/Mockery.php:85
/Users/ninnypants/Sites/search-everything-wordpress-plugin/vendor/10up/wp_mock/WP_Mock.php:81
/Users/ninnypants/Sites/search-everything-wordpress-plugin/tests/admin-tests.php:11

FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

Can't mock the same method twice within a test class

Love WP_Mock so far, it's a great tool that is helping me write better WordPress code and have better confidence in it. While I was testing a plugin I encountered the following problem.

Given a class under test like this:

<?php
class Example {

    public function sample($input) {
        //do stuff

        $sanitized = sanitize_text($input);

        //do more stuff
    }

    public function other($input) {
        //do stuff

        $sanitized = sanitize_text($input);

        //do more stuff
    }
}
?>

In order to test sample and other we need to mock sanitize_text() for which we can use the method \WP_Mock::wpPassthruFunction('sanitize_text') to check that each method calls it at least once:

<?php
class MyTestClass extends PHPUnit_Framework_TestCase {
    public function setUp() {
        \WP_Mock::setUp();
    }

    public function tearDown() {
        \WP_Mock::tearDown();
    }

    public function testSampleExpectsSanitizeTextCalledOnce() {
        // arrange
        \WP_Mock::wpPassthruFunction('sanitize_text', array('times' => 1));
        $input = 'Foo';
        $Class = new \Example();

        // act
        $Class->sample($input);
    }

    public function testOtherExpectsSanitizeTextCalledOnce() {
        // arrange
        \WP_Mock::wpPassthruFunction('sanitize_text', array('times' => 1));
        $input = 'Foo';
        $Class = new \Example();

        // act
        $Class->other($input);
    }
}
?>

When we run phpunit, the first test passes and the second test throws an error flagging a call to undefined function Patchwork\replace(). phpunit output:

.PHP Fatal error:  Call to undefined function Patchwork\replace() in .../vendor/10up/wp_mock/WP_Mock/Functions.php on line 216

Fatal error: Call to undefined function Patchwork\replace() in .../vendor/10up/wp_mock/WP_Mock/Functions.php on line 216

The solution: add \WP_Mock\Handler::cleanup() to tearDown();:

<?php
class MyTestClass extends PHPUnit_Framework_TestCase {
    public function setUp() {
        \WP_Mock::setUp();
    }

    public function tearDown() {
        \WP_Mock::tearDown();
        \WP_Mock\Handler::cleanup();
    }

Allow using Mockery syntax

I realize this may not be in line with the WP_Mock mission, but I've seen several situations where it's helpful to use a combination of using WP_Mock::wpFunction() and Mockery::mock() together. In these cases it's awkward (and a little confusing for other developers) to switch between the WP_Mock and Mockery expectation syntaxes.

To be more specific, I might write the following in a test:

\WP_Mock::wpFunction( 'get_permalink', array( 'args' => 42, 'return' => 'http://example.com/foo' ) );
\Mockery::mock( 'MyClass' )->shouldReceive( 'getLink' )->with( 42 )->andReturn( 'http://example.com/foo' );

It seems relatively easy to allow wpFunction() to return the Mockery\Mock object (by returning $expectation from Functions::set_up_mock through Functions::register). If that were done, it would be up to the user to choose which style of syntax to use, which while it is inconsistent, provides more flexibility.

Thus, I could re-write the above as:

\WP_Mock::wpFunction( 'get_permalink' )->with( 42 )->andReturn( 'http://example.com/foo' );
\Mockery::mock( 'MyClass' )->shouldReceive( 'getLink' )->with( 42 )->andReturn( 'http://example.com/foo' );

Consider this a feature request. I'd be happy to write the PR to go with it. ๐Ÿ˜„

Add expectActionNotAdded()

Right now I'm working on a project where do_action() gets triggered only when the rest of the function succeeds. It'd be great to be able to say "in this method where I'm testing how it fails the action is not called.

Edit: obviously the method name could use some work :)

Add PHPDoc to `\WP_Mock\Hook::with`

When dealing with code inspection, something like this cause some warning:

\WP_Mock::onFilter( 'my_filter' )->with( false )->reply( $my_reply );

The problem is that the returned type of the with method is unclear.

Apparently, responders does not implement any interface, so I guess that the only way to solve this is by adding this PHPDoc to the \WP_Mock\Hook::with method:

/** @return Action_Responder|Filter_Responder */
public function with() {
	$args = func_get_args();
	$responder = $this->new_responder();
	if ( $args === array( null ) ) {
		$this->processors['argsnull'] = $responder;
	} else {
		$num_args = count( $args );
		$processors = &$this->processors;
		for( $i = 0; $i < $num_args - 1; $i++ ) {
			$arg = $this->safe_offset( $args[ $i ] );
			if ( ! isset( $processors[ $arg ] ) ) {
				$processors[ $arg ] = array();
			}
			$processors = &$processors[ $arg ];
		}
		$processors[ $this->safe_offset( $args[ $num_args - 1 ] ) ] = $responder;
	}
	return $responder;
}

Could this be possible, so we can reduce the number of warnings from our IDEs?

A function mocked in one test will remain valid ("existing") during the whole test run.

This is probably not possible to fix this because of how PHP works, but I wanted to mention this issue because it can have some side effects in the sequence of tests.

How to reproduce the issue:

  1. Add a function mock in the first test class to be run:
    \WP_Mock::wpFunction( 'my_custom_function', array( 'return' => true ) );

  2. In another test class (coming after the first one), call this function:
    $test = my_custom_function();

=> $test will be set to null.

Perhaps it deserves one note in the documentation (https://github.com/10up/wp_mock).

Thanks for the great framework!

Fails with PHPUnit 6.0.6

Using the latest PHPUnit WP_Mock causes a failure with the following error:

PHP Fatal error: Class 'PHPUnit_Framework_TestCase' not found in .../vendor/10up/wp_mock/WP_Mock/Tools/TestCase.php on line 12

Can't require in WP plugin because of function conflicts

Maybe I'm using this wrong but if I require this library in my plugin file, I can use it for testing, but it also conflicts with real wordpress functions. Is there a way to autoload this for testing only and still use composer?

Expect action to be never called

I'm quite sure that, some time ago, I had found some answer to this question here or somewhere else, but I could not find it anymore and I don't remember if I had found a solution.

What I need is writing a test and expect a given action to be never called: how can I do that?

It looks like \WP_Mock::expectAction has no option where I can specify that.

Any tip?

Deprecate WP-branded methods

After #17 has been completed, we need to deprecate the WP-branded method names. They'll still work, but they should be avoided and replaced in test suites.

I'd really like to have the methods create some sort of warning for PHPUnit that would alert the user to the fact that they're using a deprecated method and need to update their tests.

Improve how argument matching works for hook expectations

Currently you can't declare expectations on things like type or fuzzy matching for arguments. If the args don't match exactly, we can't match an action or filter. I'd like to be able to do something like

WP_Mock::expectActionAdded( 'some_action', array( WP_Mock::typeMatcher( 'SomeClass' ), 'method' ) );
new SomeClass(); // Action added in constructor

Undefined index error when calling `do_action`

After WP_Mock is loaded, whenever do_action is called, a PHP error is thrown. For example, with this test in my-Test.php:

<?php

function my_wp_function() {
  do_action( 'foo', 'bar' );
}

class MyTest extends PHPUnit_Framework_TestCase
{
  public function setUp() {
    \WP_Mock::setUp();
  }

  public function tearDown() {
    \WP_Mock::tearDown();
  }

  public function testRunsCorrectly()
  {
    my_wp_function();
    $this->assertEquals(1, 1);
  }
}

If I then run phpunit --bootstrap vendor/autoload.php my-Test.php, I get the following error:

PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

E

Time: 31 ms, Memory: 3.00Mb

There was 1 error:

1) MyTest::testRunsCorrectly
Undefined index: bar

/Users/payton/tmp/wp_mock_test/vendor/10up/wp_mock/WP_Mock/Action.php:37
/Users/payton/tmp/wp_mock_test/vendor/10up/wp_mock/WP_Mock/API/function-mocks.php:35
/Users/payton/tmp/wp_mock_test/my-Test.php:4
/Users/payton/tmp/wp_mock_test/my-Test.php:19

The undefined index appears to be whatever the second argument to do_action is.

I've used WP_Mock many times before with no issues, which makes me think I'm doing something foolish here, but I can't figure out what. Please point it out to me if I'm making an obvious mistake.

Here's my composer installed packages:

installed:
  10up/wp_mock          dev-master 8f2e98a A mocking library to take the pain out of unit testing for WordPress
  antecedent/patchwork  1.3.4              A pure PHP library that lets you redefine user-defined functions at runtime.
  hamcrest/hamcrest-php v1.2.2             This is the PHP port of Hamcrest Matchers
  mockery/mockery       0.9.4              Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framew...

(I created a gist of the above failing test)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.