# Table of Contents - [Introduction | Brain Monkey](#introduction-brain-monkey) - [Setup for functions testing | Brain Monkey](#setup-for-functions-testing-brain-monkey) - [Patching functions with when() | Brain Monkey](#patching-functions-with-when-brain-monkey) - [Testing functions with expect() | Brain Monkey](#testing-functions-with-expect-brain-monkey) - [Bulk patching with stubs() | Brain Monkey](#bulk-patching-with-stubs-brain-monkey) - [Why bother | Brain Monkey](#why-bother-brain-monkey) - [Installation | Brain Monkey](#installation-brain-monkey) - [WordPress testing tools | Brain Monkey](#wordpress-testing-tools-brain-monkey) - [Setup for WordPress testing | Brain Monkey](#setup-for-wordpress-testing-brain-monkey) - [Test done hooks | Brain Monkey](#test-done-hooks-brain-monkey) - [Test added hooks | Brain Monkey](#test-added-hooks-brain-monkey) - [Migration from v1 | Brain Monkey](#migration-from-v1-brain-monkey) - [Setup for functions testing | Brain Monkey](#setup-for-functions-testing-brain-monkey) - [Why bother | Brain Monkey](#why-bother-brain-monkey) - [Installation | Brain Monkey](#installation-brain-monkey) - [Migration from v1 | Brain Monkey](#migration-from-v1-brain-monkey) --- # Introduction | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/#whats-brain-monkey) What's Brain Monkey --------------------------------------------------------------------------------------------------- Brain Monkey is a unit test utility for PHP. It comes with 2 group of features: * the first allow **mocking and testing any PHP function**. This part is a general tool and two times framework agnostic: can be used to test code that uses any frameworks (or no framework) and in combination with any testing framework. * the second group of features can be used with any testing framework as well, but is **specific to test WordPress code**. Who is interested in the first part can use only it, just like this second group of features does not exists. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/#why-brain-monkey) Why Brain Monkey ---------------------------------------------------------------------------------------------- When unit tests are done in the right way, the SUT (System Under Test) must be tested in **isolation**. Long story short, it means that any _external_ code used in the SUT must be assumed as perfectly working. This is a key concept in unit tests. In PHP, to create "mock" and "stubs" for objects is a pretty easy task, framework like [PHPUnit](https://phpunit.de/manual/current/en/test-doubles.html) or [phpspec](https://www.phpspec.net/en/latest/manual/prophet-objects.html) have embedded features to do that, and libraries like [Mockery](https://github.com/padraic/mockery) make it even easier. But when _external_ code make use of **functions** things become harder, because PHP testing framework can't mock or monkey patch functions. This is where Brain Monkey comes into play: its aim is to bring that easiness to function testing. This involves: * define functions if not defined * allow to enforce function behavior * allow to set expectations on function execution Moreover, I have to admit that I coded Brain Monkey to test WordPress code (that makes a large use of global functions). This is the reason why Brain Monkey comes with a set of WordPress-specific tools, but the ability to monkey patch and test functions is independent from WordPress-specific tools and can be used to test any PHP code. ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/#under-the-hood) Under the hood Brain Monkey gets all its power from two great libraries: [**Mockery**](http://docs.mockery.io/) and [**Patchwork**](http://patchwork2.org/) . What actually Brain Monkey does is to connect the _function redefinition_ feature of Patchwork with the powerful testing mechanism and DSL provided by Mockery, and thanks to that Brain Monkey has: * PHPUnit, PHPSpec or any other testing framework compatibility * powerful and succinct API with human readable syntax All the rest is joy. ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/#php-versions-compatibility) PHP versions compatibility Currently, Brain Monkey supports PHP 5.6+. [NextInstallation](https://giuseppe-mazzapica.gitbook.io/brain-monkey/general/installation) Last updated 6 years ago --- # Setup for functions testing | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#testing-framework-agnostic) Testing framework agnostic --------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey can be used with any testing framework. Examples in this page will use PHPUnit, but the concepts are applicable at any testing framework. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#warning) Warning ------------------------------------------------------------------------------------------------------------------- Brain Monkey uses [Patchwork](http://patchwork2.org/) to redefine functions. Brain Monkey 2.\* requires Patchwork 2 which allows to re-define both userland and core functions, with some [limitations](http://patchwork2.org/limitations/) . The main limitations that affects Brain Monkey are (from Patchwork website): * _Patchwork will fail on every attempt to redefine an internal function that is missing from the redefinable-internals array of your_ `_patchwork.json_`_._ * _Make sure that Patchwork is imported as early as possible, since any files imported earlier, including the one from which the importing takes place, will be missed by Patchwork's code preprocessor._ [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#setup-tests) Setup tests --------------------------------------------------------------------------------------------------------------------------- After Brain Monkey is part of the project (see _Getting Started / Installation_), to be able to use its features two simple steps are needed before being able to use Brain Monkey in tests: 1. be sure to require Composer autoload file _before_ running tests (e.g. PHPUnit users will probably require it in their bootstrap file). 2. call the function `Brain\Monkey\tearDown()` after any test ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#phpunit-example) PHPUnit example Let's take PHPUnit as example, the average test case class that uses Brain Monkey would be something like: Copy use PHPUnit_Framework_TestCase; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Brain\Monkey; class MyTestCase extends PHPUnit_Framework_TestCase { // Adds Mockery expectations to the PHPUnit assertions count. use MockeryPHPUnitIntegration; protected function tearDown() { Monkey\tearDown(); parent::tearDown(); } } After that for all test classes can extend this class instead of directly extending `PHPUnit_Framework_TestCase`. That's all. Again, I used PHPUnit for the example, but any testing framework can be used. For function mocking and testing there are two entry-point functions: * `**Functions\when()**` * `**Functions\expect()**` See dedicated documentation pages. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#namespaced-functions) Namespaced functions --------------------------------------------------------------------------------------------------------------------------------------------- All the code examples in this documentation make use of functions in global namespace. However, note that namespaced functions are supported as well, just be sure to pass the fully qualified name of the functions: Copy Functions\expect('a_global_function'); Functions\expect('My\\App\\awesome_function'); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup#note-for-wordpressers) Note for WordPressers ----------------------------------------------------------------------------------------------------------------------------------------------- Anything said in this page is fine for WordPress functions too, they are PHP functions, after all. However, Brain Monkey has specific features for WordPress, and there is a way to setup tests for **all** Brain Monkey features (WordPress-specific and not). **If you want to use Brain Monkey to test code wrote for WordPress, it is preferable to use the setup explained in the** _**"WordPress / Setup"**_ **section that** _**includes**_ **the setup needed to use Brain Monkey tools for functions.** [PreviousInstallation](https://giuseppe-mazzapica.gitbook.io/brain-monkey/general/installation) [NextPatching functions with when()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when) Last updated 6 years ago --- # Patching functions with when() | Brain Monkey The first way Brain Monkey offers to monkey patch a function is `Functions\when()`. This function has to be used to **set a behavior** for functions. `when()` and 5 related methods are used to define functions (if not defined yet) and: * make them return a specific value * make them return one of the received arguments * make them echo a specific value * make them echo one of the received arguments * make them behave just like another callback For the sake of readability, in all the code samples below I'll assume that an `use` statement is in place: Copy use Brain\Monkey\Functions; Don't forget to add it in your code as well, or use the fully qualified class name. Also be sure to read the _PHP Functions / Setup_ section that explain how setup Brain Monkey for usage in tests. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#justreturn) `justReturn()` ---------------------------------------------------------------------------------------------------------------------------- By using `when()` in combination with `justReturn()` you can make a (maybe) undefined function _just return_ a given value: Copy Functions\when('a_undefined_function')->justReturn('Cool!'); echo a_undefined_function(); // echoes "Cool!" Without passing a value to `justReturn()` the target function will return nothing (`null`). [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#returnarg) `returnArg()` -------------------------------------------------------------------------------------------------------------------------- This other `when`\-related method is used to make the target function return one of the received arguments, by default the first. Copy Functions\when('give_me_the_first')->returnArg(); // is the same of ->returnArg(1) Functions\when('i_want_the_second')->returnArg(2); Functions\when('and_the_third_for_me')->returnArg(3); echo give_me_the_first('A', 'B', 'C'); // echoes "A" echo i_want_the_second('A', 'B', 'C'); // echoes "B" echo and_the_third_for_me('A', 'B', 'C'); // echoes "C" Note that if the target function does not receive the desired argument, `returnArg()` throws an exception: Copy Functions\when('needs_the_third')->returnArg(3); // throws an exception because required 3rd argument, but received 2 echo needs_the_third('A', 'B'); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#justecho) `justEcho()` ------------------------------------------------------------------------------------------------------------------------ Similar to `justReturn()`, it makes the mocked function echo some value instead of returning it. Copy Functions\when('a_undefined_function')->justEcho('Cool!'); a_undefined_function(); // echoes "Cool!" [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#echoarg) `echoArg()` ---------------------------------------------------------------------------------------------------------------------- Similar to `returnArg()`, it makes the mocked function echo some received argument instead of returning it. Copy Functions\when('echo_the_first')->echoArg(); // is the same of ->echoArg(1) Functions\when('echo_the_second')->echoArg(2); echo_the_first('A', 'B', 'C'); // echoes "A" echo_the_second('A', 'B', 'C'); // echoes "B" [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#alias) `alias()` ------------------------------------------------------------------------------------------------------------------ The last of the when-related methods allows to make a function behave just like another callback. The replacing function can be anything that can be run: a core function or a custom one, a class method, a closure... Copy Functions\when('duplicate')->alias(function($value) { return "Was ".$value.", now is ".($value * 2); }); Functions\when('bigger')->alias('strtoupper'); echo duplicate(1); // echoes "Was 1, now is 2" echo bigger('was lower'); // echoes "WAS LOWER" [PreviousSetup for functions testing](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup) [NextBulk patching with stubs()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs) Last updated 2 years ago --- # Testing functions with expect() | Brain Monkey Often, in tests, what we need is not only to enforce a function returned value (what `Functions\when()` allows to do), but to test function behavior based on **expectations**. Mockery has a very powerful, and human readable Domain Specific Language (DSL) that allows to set expectations on how object methods should behave, e.g. validate arguments they should receive, how many times they are called, and so on. Brain Monkey brings that power to function testing. The entry-point is the `Functions\expect()` function. It receives a function name and returns a Mockery expectation object with all its power. Below there are just several examples, for the full story about Mockery expectations see its [documentation](http://docs.mockery.io/en/latest/reference/index.html) . Only note that in functions testing the `shouldReceive` Mockery method makes **no sense**, so don't use it (an exception will be thrown if you do that). [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect#expectations-on-times-a-function-is-called) Expectations on times a function is called ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Copy Functions\expect('paganini')->once(); Functions\expect('tween')->twice(); Functions\expect('who_knows')->zeroOrMoreTimes(); Functions\expect('i_should_run')->atLeast()->once(); Functions\expect('i_have_a_max')->atMost()->twice(); Functions\expect('poor_me')->never(); Functions\expect('pretty_precise')->times(3); Functions\expect('i_have_max_and_min')->between(2, 4); There is no need to explain how it works: Mockery DSL reads like plain English. Of course, expectation on the times a function should run can be combined with arguments expectation. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect#expectations-on-received-arguments) Expectations on received arguments -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Below a few examples, for the full story see [Mockery docs](http://docs.mockery.io/en/latest/reference/argument_validation.html) . Copy // allow anything Functions\expect('function_name') ->once() ->withAnyArgs(); // allow nothing Functions\expect('function_name') ->once() ->withNoArgs(); // validate specific arguments Functions\expect('function_name') ->once() ->with('arg_1', 'arg2'); // validate specific argument types Functions\expect('function_name') ->times(3) ->with(Mockery::type('resource'), Mockery::type('int')); // validate anything in specific places Functions\expect('function_name') ->zeroOrMoreTimes() ->with(Mockery::any()); // validate a set of given arguments Functions::expect('function_name') ->once() ->with(Mockery::anyOf('a', 'b', 'c')); // regex validation Functions\expect('function_name') ->once() ->with('/^foo/'); // excluding specific values Functions\expect('function_name') ->once() ->with(Mockery::not(2, 3)); // dealing with array arguments Functions\expect('function_name') ->once() ->with(Mockery::hasKey('foo'), Mockery::contains('bar', 'baz')); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect#forcing-behavior) Forcing behavior -------------------------------------------------------------------------------------------------------------------------------------- Excluding `shouldReceive`, all the Mockery expectation methods can be used with Brain Monkey, including `andReturn` or `andReturnUsing` used to enforce a function to return specific values during tests. In fact, `Functions\when()` do same thing for simple cases when no expectations are required. Again, just a few examples: Copy // return a specific value Functions\expect('function_name') ->once() ->with('foo', 'bar') ->andReturn('Baz!'); // return values in order Functions\expect('function_name') ->twice() ->andReturn('First time I run', 'Second time I run'); // return values in order, alternative Functions\expect('function_name') ->twice() ->andReturnValues(['First time I run', 'Second time I run']); // return noting Functions::expect('function_name') ->twice() ->andReturnNull(); // use a callback for returning a value Functions\expect('function_name') ->atLeast() ->once() ->andReturnUsing(function() { return 'I am an alias!'; }); // makes function throws an Exception (e.g. to test try statements) Functions\expect('function_name') ->once() ->andThrow('RuntimeException'); // Both exception names and object are supported [PreviousBulk patching with stubs()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs) [NextWhy bother](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-why-bother) Last updated 6 years ago --- # Bulk patching with stubs() | Brain Monkey `when()` and its related functions are quite simple and straightforward. However, it can be quite verbose when multiple functions needs to be patched. For this reason, version 2.1 introduced a new API function to define multiple functions in bulk: `stubs()` ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#stubs) `stubs()` `Functions\stubs()` accepts an array of functions to be defined. The first way to use it is to pass function names as array item _keys_ and the wanted return values as array _values_: Copy Functions\stubs( [\ 'is_user_logged_in' => true,\ 'current_user_can' => false,\ ] ); There are two special cases: * when the array item value is a `callable`, the function given as array item key is _aliased_ to the given callback instead of returning the callback itself; * when the array item value is `null`, the function given as array item key will return the first argument received, just like `when( $function_name )->justReturnArg()` was used for it Copy Functions\stubs( [\ 'is_user_logged_in' => true, // will return `true` as provided\ 'wp_get_current_user' => function () { // will return the WP_User mock\ return \Mockery::mock(\WP_User::class);\ },\ '__' => null, // will return the 1st argument received\ ] ); Another way to use `stubs`, useful to stub many function with same return value, is to pass to a non-associative array of function names as first argument, and the wanted return value for all of them as second argument. For example, the snippet below will create a stub that returns `true` for all the given functions: Copy Functions\stubs( [\ 'is_user_logged_in',\ 'current_user_can',\ 'is_multisite',\ 'is_admin',\ ], true ); Please note that the default value for the second argument, being it optional, is `null`, and because using `null` as value means _"return first received argument"_ it is possible to stub many functions that have to return first received argument, by passing their names as first argument to `stubs()` (and no second argument), like this: Copy Functions\stubs( [\ 'esc_attr',\ 'esc_html',\ '__',\ '_x',\ 'esc_attr__',\ 'esc_html__',\ ] ); (Even if there's a simpler way to stub escaping and translation WP functions, more on this below). It worth noting that the two ways of using `stubs()` can be mixed together, for example like this: Copy Functions\stubs( [\ // will both return 1st argument received, because `stubs` 2nd param defaults to `null`\ 'esc_attr',\ 'esc_html',\ \ // will all return what is given as array item value\ 'is_user_logged_in' => true,\ 'current_user_can' => false,\ 'get_current_user_id' => 1,\ ] ); ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#pre-defined-stubs-for-escaping-functions) Pre-defined stubs for escaping functions To stub WordPress escaping functions is a very common usage for `Functions\stubs`. This is why, since version 2.3, Brain Monkey introduced a new API function: * `**Functions\stubEscapeFunctions()**` When called, it will create a stub for each of the following functions: * `esc_js()` * `esc_sql()` * `esc_attr()` * `esc_html()` * `esc_textarea()` * `esc_url()` * `esc_url_raw()` * `esc_xml()` (since 2.6) By calling `Functions\stubEscapeFunctions()`, for _all_ of the functions listed above a stub will be created that will do some very basic escaping on the received first argument before returning it. It will _not_ be the exact same escape mechanism that WordPress would apply, but "similar enough" for unit tests purpose and could still be helpful to discover some bugs. ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#pre-defined-stubs-for-translation-functions) Pre-defined stubs for translation functions Another common usage for `Functions\stubs`, since its introduction, has been to stub translation functions. Since version 2.3, this has became much easier thanks to the introduction of a new API function: * `**Functions\stubTranslationFunctions()**` When called, it will create a stub for _all_ the following functions: * `__()` * `_e()` * `_ex()` * `_x()` * `_n()` (since 2.6) * `_nx()` (since 2.6) * `translate()` * `esc_html__()` * `esc_html_x()` * `esc_attr__()` * `esc_attr_x()` * `esc_html_e()` * `esc_attr_e()` * `_n_noop()` (since 2.7) * `_nx_noop()` (since 2.7) * `translate_nooped_plural()` (since 2.7) The created stub will not attempt any translation, but will return (or echo) the first received argument. Only for functions that both translate and escape (`esc_html__()`, `esc_html_x()`...) the same escaping mechanism used by the pre-defined escaping functions stubs (see above) is applied before returning first received argument. Please note how `Functions\stubTranslationFunctions()` creates stubs for functions that _echo_ translated text, something not easily doable with `Functions\stubs()` alone. ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#gotcha-for-functions-stubs) Gotcha for `Functions\stubs` #### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#functions-that-returns-null) Functions that returns null When using `stubs()`, passing `null` as the "value" of the function to stub, the return value of the stub will **not** be `null`, but the first received value. To use `stubs()` to stub functions that return `null` it is possible to do something like this: Copy Functions\stubs( [ 'function_that_returns_null' => '__return_null' ] ); It works because `__return_null` is a WP function that Brain Monkey also defines since version 2.0. #### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#functions-that-returns-callbacks) Functions that returns callbacks When using `stubs`, passing a `callable` as the "value" of the function to stub, the created stub will be an _alias_ of the given callable, will **not** return it. If one want to use `stubs` to stub a function that returns a callable, a way to do it would be something like this: Copy Functions\stubs( [\ 'function_that_returns_a_callback' => function() { \ return 'the_expected_returned_callback';\ }\ ] ); but it is probably simpler to use the "usual" `when` + `justReturn`: Copy when('function_that_returns_a_callback')->justReturn('the_expected_returned_callback') [PreviousPatching functions with when()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when) [NextTesting functions with expect()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect) Last updated 2 years ago --- # Why bother | Brain Monkey Just to be clear, Brain Monkey is useful for testing code wrote _for_ WordPress (plugin, themes) not WordPress core. More specifically, it is useful to run **unit tests**. Integration tests or end-to-end tests are a thing: you need to be sure that your code works good _with_ WordPress. But **unit** tests are meant to be run **without loading WordPress environment**. Every component that is unit tested, should be tested in isolation: when you test a class, you only have to test that specific class, assuming all other code (e.g. WordPress code) is working perfectly. This is not only because doing that tests will run much faster, but also because the key concept in unit testing is that every piece of code should work _per se_, in this way if a test fails there is only one possible culprit. By assuming all the external code is working perfectly, it is possible to test the behavior of the SUT (System Under Test), without any _interference_. To deepen these concepts, read [this answer](https://wordpress.stackexchange.com/a/164138/35541) I wrote for WordPress Development (StackExchange) site, that also contains some tips to write better _testable_ WordPress code. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-why-bother#if-wordpress-is-not-loaded) If WordPress is not loaded... ------------------------------------------------------------------------------------------------------------------------------------------------------------------ WordPress functions are not available, and trying to run tests in that situation, tests fail with fatal errors. Unless you use Brain Monkey. It allows to mock WordPress function (just like any PHP function), and to check how they are called inside your code. See the _PHP Function_ documentation section for a deep explanation on how it works. Moreover, among others, WordPress [Plugin API functions](https://codex.wordpress.org/Plugin_API) are particularly important and a very fine grained control on how they are used in code is pivotal to proper test WordPress extensions. This is why Brain Monkey comes with a set of features specifically designed for that. [PreviousTesting functions with expect()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect) [NextWordPress testing tools](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-tools) Last updated 6 years ago --- # Installation | Brain Monkey To install Brain Monkey you need: * PHP 5.6+ * [Composer](https://getcomposer.org/) Brain Monkey is available on Packagist, so the only thing you need to do is to add it as a dependency for your project. That can be done by running following command in your project folder: Copy composer require brain/monkey:2.* --dev As alternative you can directly edit your `composer.json` by adding: Copy { "require-dev": { "brain/monkey": "~2.0.0" } } I've used `require-dev` because, being a testing tool, Brain Monkey should **not** be included in production. Brain Monkey can work with any testing framework, so it doesn't require any of them. To run your tests you'll probably need to require a testing framework too, e.g. [PHPUnit](https://phpunit.de/) or [phpspec](https://www.phpspec.net/en/latest/) . [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/general/installation#dependencies) Dependencies ---------------------------------------------------------------------------------------------------------- Brain Monkey needs 2 libraries to work: * [Mockery](http://docs.mockery.io/en/latest/) (BSD-3-Clause) * [Patchwork](http://patchwork2.org/) (MIT) They will be installed for you by Composer. When installed in development mode (to test itself), Brain Monkey also requires: * [PHPUnit](https://phpunit.de/) (MIT) [PreviousIntroduction](https://giuseppe-mazzapica.gitbook.io/brain-monkey) [NextSetup for functions testing](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup) Last updated 6 years ago --- # WordPress testing tools | Brain Monkey The sole ability to mocking functions is a great help on testing WordPress code. All WordPress functions can be mocked and tested using the techniques described in the _PHP Functions_ section, they are PHP functions, after all. However, to test WordPress code in isolation, without a bunch of bootstrap code for every test, a more fine grained control of plugin API functions is required. This is exactly what Brain Monkey offers. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-tools#defined-functions) Defined functions ---------------------------------------------------------------------------------------------------------------------------------------- Following functions are defined by Brain Monkey when it is loaded for tests: **Hook-related functions:** * `add_action()` * `remove_action()` * `do_action()` * `do_action_ref_array()` * `do_action_deprecated()` (since 2.4) * `did_action()` * `doing_action()` * `has_action()` * `add_filter()` * `remove_filter()` * `apply_filters()` * `apply_filters_ref_array()` * `apply_filters_deprecated()` (since 2.4) * `doing_filter()` * `has_filter()` * `current_filter()` **Generic functions:** * `__return_true()` * `__return_false()` * `__return_null()` * `__return_zero()` * `__return_empty_array()` * `__return_empty_string()` * `trailingslashit()` * `untrailingslashit()` * `user_trailingslashit()` (since 2.6) * `absint()` (since 2.3) * `wp_json_encode()` (since 2.6) * `is_wp_error()` (since 2.3) * `wp_validate_boolean()` (since 2.7) * `wp_slash()` (since 2.7) **Translation function:** Since Brain Monkey 2.3, stubs for the standard WordPress translations functions are available via `Functions\stubEscapeFunctions()`. See: [Pre-defined stubs for translation functions](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#pre-defined-stubs-for-translation-functions) **Escaping functions:** Since Brain Monkey 2.3, stubs for the standard WordPress escaping functions are available via `Functions\stubTranslationFunctions()`. See: [Pre-defined stubs for escaping functions](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/function-stubs#pre-defined-stubs-for-escaping-functions) If your code uses any of these functions, and very likely it does, you don't need to define (or mock) them to avoid fatal errors during tests. Note that the returning value of those functions (_most of the times_) will work out of the box as you might expect. For example, if your code contains: Copy do_action('my_custom_action'); // something in the middle $did = did_action('my_custom_action'); the value of `$did` will be correctly `1` (`did_action()` in WordPress returns the number an action was _done_). Or if your code contains: Copy $post = [ 'post_title' => 'My Title' ]; $title = apply_filters('the_title', $post['post_title']); the value of `$title` will be `'My Title'`, without the need of any intervention. This works as long as there's no code that actually adds filters to `"the_title"` hook, so we expect that the title stay unchanged. And that's what happen. If in the code under test there's something that adds filters (i.e. calls `add_filter`), the _Brain Monkey version_ of `apply_filters` will still return the value unchanged, but will allow to test that `apply_filters` has been called, how many times, with which callbacks and arguments are used. More generally, with regards to the WP hook API, Brain Monkey allows to: * test if an action or a filter has been added, how many times that happen and with which arguments * test if an action or a filter has been fired, how many times that happen and with which arguments * perform some callback when an action is fired, being able to access passed arguments * perform some callback when an filter is applied, being able to access passed arguments and to return specific values And it does that using its straightforward and human-readable syntax. [PreviousWhy bother](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-why-bother) [NextSetup for WordPress testing](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-setup) Last updated 2 years ago --- # Setup for WordPress testing | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-setup#testing-framework-agnostic) Testing framework agnostic ---------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey can be used with any testing framework. Examples in this page will use PHPUnit, but the concepts are applicable to any testing framework. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-setup#warning) Warning -------------------------------------------------------------------------------------------------------------------- The procedure below **includes** the setup needed for testing PHP functions, so there is **no** need to apply what said here and _additionally_ what said in the section _PHP Functions / Setup_: steps below are enough to use all Brain Monkey features, including functions utilities. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-setup#setup-tests) Setup tests ---------------------------------------------------------------------------------------------------------------------------- After Brain Monkey is part of the project (see _Getting Started / Installation_), to be able to use its features you need to **require vendor autoload file** before running tests (e.g. PHPUnit users will probably require it in their bootstrap file). After that, you need to call a function _before_ any test, and another _after_ any test. These two functions are: * `Brain\Monkey\setUp()` has to be run before any test * `Brain\Monkey\tearDown()` has to be run after any test PHPUnit users will probably want to add these methods to a custom test case class: Copy use PHPUnit_Framework_TestCase; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Brain\Monkey; class MyTestCase extends PHPUnit_Framework_TestCase { // Adds Mockery expectations to the PHPUnit assertions count. use MockeryPHPUnitIntegration; protected function setUp() { parent::setUp(); Monkey\setUp(); } protected function tearDown() { Monkey\tearDown(); parent::tearDown(); } } and then extend various test classes from it instead of directly extend `PHPUnit_Framework_TestCase`. That's all. You are ready to use all Brain Monkey features. [PreviousWordPress testing tools](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-tools) [NextTest added hooks](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added) Last updated 6 years ago --- # Test done hooks | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#testing-framework-agnostic) Testing framework agnostic --------------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey can be used with any testing framework. Examples in this page will use PHPUnit, but the concepts are applicable to any testing framework. Also note that test classes in this page extends the class `MyTestCase` that is assumed very similar to the one coded in the _WordPress / Setup_ docs section. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#simple-tests-with-did_action-and-filters-applied) Simple tests with `did_action()` and `Filters\applied()` ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To check hooks have been fired, the only available WordPress function is `did_action()`, it doesn't exist any `did_filter()` or `applied_filter()`. To overcome the missing counter part of `did_action()` for filters, Brain Monkey has a method accessible via `Brain\Monkey\Filters\applied()` that does what you might expect. Assuming a class like the following: Copy class MyClass { function fireHooks() { do_action('my_action', $this); return apply_filters('my_filter', 'Filter applied', $this); } } It can be tested using: Copy use Brain\Monkey\Filters; class MyClassTest extends MyTestCase { function testFireHooksActuallyFiresHooks() { ( new MyClass() )->fireHooks(); $this->assertSame( 1, did_action('my_action') ); $this->assertTrue( Filters\applied('my_filter') > 0 ); } } As you can guess from test code above, `did_action()` and `Filters\applied()` return the number of times an action or a filter has been triggered, just like `did_action()` does in WordPress, but there's no way to use them to check which arguments were passed to the fired hook. So, `did_action()` and `Filters\applied()` are fine for simple tests, mostly because using them you don't need to recall Brain Monkey methods, but they are not very powerful: arguments checking and, above all, the ability to respond to fired hooks are pivotal tasks to proper test WordPress code. In Brain Monkey those tasks can be done testing fired hooks with expectations. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#test-fired-hooks-with-expectations) Test fired hooks with expectations ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A powerful testing mechanism for fired hooks is provided by Brain Monkey thanks to Mockery expectations. The entry points to use it are the `Actions\expectDone()` and `Filters\expectApplied()` functions. As usual, below there a just a couple of examples, for the full story see [Mockery docs](http://docs.mockery.io/en/latest/reference/expectations.html) . Assuming the `MyClass` above in this page, it can be tested with: Copy use Brain\Monkey\Actions; use Brain\Monkey\Filters; class MyClassTest extends MyTestCase { function testFireHooksActuallyFiresHooks() { Actions\expectDone('my_action') ->once() ->with(Mockery::type(MyClass::class)); Filters\expectApplied('my_filter') ->once() ->with('Filter applied', Mockery::type(MyClass::class)); ( new MyClass() )->fireHooks(); } } [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#just-a-couple-of-things) Just a couple of things... ------------------------------------------------------------------------------------------------------------------------------------------------------------ * expectations must be set _before_ the code to be tested runs: they are called "expectations" for a reason * argument validation done using `with()`, validates hook arguments, not function arguments, it means what is passed to `do_action` or `apply_filters` **excluding** hook name itself [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#respond-to-filters) Respond to filters ----------------------------------------------------------------------------------------------------------------------------------------------- Yet again, Brain Monkey, when possible, tries to make WordPress functions it redefines behave in the same way of _real_ WordPress functions. Brain Monkey `apply_filters` by default returns the first argument passed to it, just like WordPress function does when no callback is added to the filter. However, sometimes in tests is required that a filter returns something different. Luckily, Mockery provides `andReturn()` and `andReturnUsing()` expectation methods that can be used to make a filter return anything. Copy use Brain\Monkey\Filters; class MyClassTest extends MyTestCase { function testFireHooksReturnValue() { Filters\expectApplied('my_filter') ->once() ->with('Filter applied', Mockery::type(MyClass::class)) ->andReturn('Brain Monkey rocks!'); $class = new MyClass(); $this->assertSame('Brain Monkey rocks!', $class->fireHooks()); } } See [Mockery docs](http://docs.mockery.io/en/latest/reference/expectations.html) for more information. Brain Monkey also provides the helper `andReturnFirstArg()` that can be used to make a filter expectation behave like WordPress does: return first argument received: Copy Filters\expectApplied('my_filter')->once()->andReturnFirstArg(); self::assertSame( 'foo', apply_filters( 'my_filter', 'foo', 'bar' ) ); Note that in the example right above, the expectation would not be necessary; in fact, the assertion verify either way because it is the default behavior of WordPress and Brain Monkey. But this is very helpful what we want to set expectations and returned values for filters based on some received arguments, for example: Copy Filters\expectApplied('my_filter')->once()->with('foo')->andReturnFirstArg(); Filters\expectApplied('my_filter')->once()->with('bar')->andReturn('This time bar!'); self::assertSame( 'Foo', apply_filters( 'my_filter', 'Foo' ) ); self::assertSame( 'This time bar!', apply_filters( 'my_filter', 'Bar' ) ); Finally note that when setting different expectations for same filter, but for different received arguments, an expectation is required to be set for **all** the arguments that the filter is going to receive. For example this will fail: Copy Filters\expectApplied('my_filter')->once()->with('foo')->andReturnFirstArg(); Filters\expectApplied('my_filter')->once()->with('bar')->andReturn('This time bar!'); self::assertSame( 'Foo', apply_filters( 'my_filter', 'Foo' ) ); self::assertSame( 'This time bar!', apply_filters( 'my_filter', 'Bar' ) ); self::assertSame( 'Meh!', apply_filters( 'my_filter', 'Meh!' ) ); The reason for failing is that there's no expectation set when the filter receives `"Meh!"`. In such case, `andReturnFirstArg()` comes useful again, to set a "catch all" expectation: Copy Filters\expectApplied('my_filter')->once()->with('bar')->andReturn('This time bar!'); // Catch all the other cases with the default: Filters\expectApplied('my_filter')->once()->withAnyargs()->andReturnFirstArg(); // All the following passes! self::assertSame( 'Foo', apply_filters( 'my_filter', 'Foo' ) ); self::assertSame( 'This time bar!', apply_filters( 'my_filter', 'Bar' ) ); self::assertSame( 'Meh!', apply_filters( 'my_filter', 'Meh!' ) ); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#respond-to-actions) Respond to actions ----------------------------------------------------------------------------------------------------------------------------------------------- To return a value from a filter is routine, not so for actions. In fact, `do_action()` always returns `null` so, if Brain Monkey would allow a _mocked_ returning value for `do_action()` expectations, it would be in contrast with real WordPress code, with disastrous effects on tests. So, don't try to use neither `andReturn()` or `andReturnUsing()` with `Actions\expectDone()` because it will throw an exception. However, sometimes one may be in the need do _something_ when code calls `do_action()`, like WordPress actually does. This is the reason Brain Monkey introduces `whenHappen()` method for action expectations. The method takes a callback to be ran when an action is fired. Let's assume a class like the following: Copy class MyClass { public $post; function setPost() { global $post; $this->post = $post; do_action('my_class_set_post', $this); return $post; } } It is possible write a test like this: Copy use Brain\Monkey\Actions; class MyClassTest extends MyTestCase { function testFireHooksReturnValue() { Action\expectDone('my_class_set_post') ->with(Mockery::type(MyClass::class)) ->whenHappen(function($my_class) { $my_class->post = (object) ['post_title' => 'Mocked!']; }); ( new MyClass() )->setPost(); $this->assertSame( 'Mocked!', $class->post->post_title ); } } [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done#resolving-current_filter-doing_action-and-doing_filter) Resolving `current_filter()`, `doing_action` and `doing_filter()` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When WordPress is not performing an hook, `current_filter()` returns `false`. And so does the Brain Monkey version of that function. Now I want to surprise you: `current_filter()` correctly resolves to the correct hook during the execution of any callback added to respond to hooks. Let's assume a class like the following: Copy class MyClass { function getValues() { $title = apply_filters('my_class_title', ''); $content = apply_filters('my_class_content', ''); return [$title, $content]; } } It is possible write a test like this: Copy use Brain\Monkey\Filters; class MyClassTest extends MyTestCase { function testGetValues() { $callback = function() { return current_filter() === 'my_class_title' ? 'Title' : 'Content'; }; Filters\expectApplied('my_class_title')->once()->andReturnUsing($callback); Filters\expectApplied('my_class_content')->once()->andReturnUsing($callback); $class = new MyClass(); $this->assertSame(['Title', 'Content'], $class->getValues()); } } Like magic, inside our callback, `current_filter()` returns the right hook just like it does in WordPress. Note this will also work with any callback passed to `whenHappen()`. Surprised? There's more: inside callbacks used to respond to actions and filters, `doing_action()` and `doing_filter()` works as well! Assuming a class like the following: Copy class MyClass { function doStuff() { do_action( 'trigger_an_hook' ); } } It is possible to write a test like this: Copy use Brain\Monkey\Actions; class MyClassTest extends MyTestCase { function testDoStuff() { // 'an_hook' action is done below in the "whenHappen" callback Actions\expectDone( 'an_hook' )->once()->whenHappen(function() { self::assertTrue( doing_action('an_hook') ); // doing_action() also resolves the "parent" hook like it was WordPress! self::assertTrue( doing_action('trigger_an_hook') ); }); Actions\expectDone('trigger_an_hook')->once()->whenHappen(function() { if( current_filter() === 'trigger_an_hook' ) { do_action('an_hook'); } }); } } [PreviousTest added hooks](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added) [NextMigration from v1](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1) Last updated 6 years ago --- # Test added hooks | Brain Monkey With Brain Monkey there are two ways to test some hook have been added, and with which arguments. First method (easier) makes use of WordPress functions, the second (more powerful) makes use of Brain Monkey (Mockery) expectation DSL. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#testing-framework-agnostic) Testing framework agnostic ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey can be used with any testing framework. Examples in this page will use PHPUnit, but the concepts are applicable to any testing framework. Also note that test classes in this page extends the class `MyTestCase` that is assumed very similar to the one coded in the _WordPress / Setup_ docs section. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#testing-with-wordpress-functions-has_action-and-has_filter) Testing with WordPress functions: `has_action()` and `has_filter()` ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When Brain Monkey is loaded for tests it registers all the functions of WordPress plugin API (see _WordPress / WordPress Testing Tools_). Among them there are `has_action()` and `has_filter()` that, just like _real_ WordPress functions can be used to test if some hook (action or filter) has been added, and also verify the arguments. Let's assume the code to be tested is: Copy namespace Some\Name\Space; class MyClass { public function addHooks() { add_action('init', [__CLASS__, 'init'], 20); add_filter('the_title', [__CLASS__, 'the_title'], 99); } } in Brain Monkey, just like in real WordPress code, you can test hooks are added using WordPress functions: Copy use Some\Name\Space\MyClass; class MyClassTest extends MyTestCase { public function testAddHooksActuallyAddsHooks() { ( new MyClass() )->addHooks(); self::assertNotFalse( has_action('init', [ MyClass::class, 'init' ]) ); self::assertNotFalse( has_filter('the_title', [ MyClass::class, 'the_title' ] ) ); } } Nice thing of this approach is that you don't need to remember Brain Monkey classes and methods names, you can just use functions you, as a WordPress developer, are already used to use. There's more. A problem of WordPress hooks is that when dynamic object methods or anonymous functions are used, identify them is not easy. It's pretty hard, to be honest. But Brain Monkey is not WordPress, and it makes these sort of things very easy. Let's assume the code to test is: Copy namespace Some\Name\Space; class MyClass { public function init() { /* ... */ } public function addHooks() { add_action('init', [ $this, 'init' ], 20); } } Using real WordPress functions, to check hooks added like in code above is pretty hard, because we don't have access to `$this` outside of the class. But Brain Monkey version of `has_action` and `has_filter` allow to check this cases with a very intuitive syntax: Copy class MyClassTest extends MyTestCase { public function testAddHooksActuallyAddsHooks() { $class = new \Some\Name\Space\MyClass\MyClass(); $class->addHooks(); self::assertSame( 20, has_action( 'init', 'Some\Name\Space\MyClass->init()' ) ); } } So we have identified a dynamic method by using the class name, followed by `->` and the method name followed by parenthesis. Moreover * a static method can be identified by the class name followed by `::` and the method name followed by parenthesis, e.g. `'Some\Name\Space\MyClass::init()'` * an invokable object (a class with a `__invoke()` method) can be identified by the class name followed by parenthesis, e.g. `'Some\Name\Space\MyClass()'` Note that fully qualified names of classes are used and namespace. ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#identify-closures) Identify Closures One tricky thing when working with hooks and closures in WordPress is that they are hard to identify, for example to remove or even to check via `has_action()` / `has_filter()` if a specific closure has been added to an hook. Brain Monkey makes this a bit easier thanks to a sort of "serialization" of closures: a closure can be identified by a string very similar to the PHP code used to define the closure. Hopefully, an example will make it more clear. Assuming a code like: Copy namespace Some\Name\Space; class MyClass { public function addHooks() { add_filter('the_title', function($title) { return $title; }, 99); } } It could be tested with: Copy class MyClassTest extends MyTestCase { public function testAddHooksActuallyAddsHooks() { $class = new \Some\Name\Space\MyClass(); $class->addHooks(); self::assertNotFalse( has_filter('the_title', 'function ($title)' ) ); } } It also works with type-hints and variadic arguments. E.g. a closure like: Copy namespace Foo\Bar; function( array $foo, Baz $baz, Bar ...$bar) { // .... } could be identified like this: Copy 'function ( array $foo, Foo\Bar\Baz $baz, Foo\Bar\Bar ...$bar )'; Just note how classes used in type-hints were using _relative_ namespace on declaration, always need the fully qualified name in the closure string representation. PHP 7+ scalar type hints are perfectly supported. The serialization also recognizes `static` closures. Following closure: Copy static function( int $foo, Bar ...$bar ) { // .... } could be identified like this: Copy 'static function ( int $foo, Bar ...$bar )'; Things that are **not** took into account during serialization: * default values for arguments * PHP 7+ return type declarations For example **all** following closures: Copy function( array $foo, $bar ) { // .... } function( array $foo = [], $bar = null ) { // .... } function( array $foo, $bar ) : array { // .... } function( array $foo, $bar = null ) : array { // .... } are serialized into : Copy 'function ( array $foo, $bar )'; [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#testing-with-expectations) Testing with expectations -------------------------------------------------------------------------------------------------------------------------------------------------------------- Even if the doing tests using WordPress native functions is pretty easy, there are cases in which is not enough powerful, or the expectation methods are just more convenient. Moreover, Brain Monkey functions always try to mimic WordPress real functions behavior and so a call to `remove_action` or `remove_filter` can make impossible to test some code using `has_action` and `has_filter`, because hooks are actually removed. The solution is to use expectations, provided in Brain Monkey by Mockery. Assuming the class to test is: Copy namespace Some\Name\Space; class MyClass { public function addHooks() { add_action('init', [$this, 'init']); add_filter('the_title', function($title) { return $title; }, 99); } } it can be tested like so: Copy use Brain\Monkey\Actions; use Brain\Monkey\Filters; class MyClassTest extends MyTestCase { function testAddHooksActuallyAddsHooks() { Actions\expectAdded('init'); Filters\expectAdded('the_title')->with(\Mockery::type('Closure')); // let's use the code that have to satisfy our expectations ( new \Some\Name\Space\MyClass() )->addHooks(); } } This is just an example, but Mockery expectations are a very powerful testing mechanism. To know more, read [Mockery documentation](http://docs.mockery.io/en/latest/) , and have a look to _PHP Functions_ doc section to see how it is used seamlessly in Brain Monkey. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#just-a-couple-of-things) Just a couple of things... ------------------------------------------------------------------------------------------------------------------------------------------------------------- * expectations must be set _before_ the code to be tested runs: they are called "expectations" for a reason; * argument validation done using `with()`, validates hook arguments, not function arguments, it means what is passed to `add_action()` or `add_filter()` **excluding** hook name itself. * If you are errors related to `Call to undefined function add_action()` it could have to do with how you are loading your plugin file in the bootstrap.php file. See [some tips for procedural/OOP setup](https://github.com/Brain-WP/BrainMonkey/issues/90#issuecomment-745148097) . [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-added#dont-set-expectations-on-return-values-for-added-hooks) Don't set expectations on return values for added hooks ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Maybe you already know that `add_action()` and `add_filter()` always return `true`. As already said, Brain Monkey always tries to make WordPress functions behave how they do in real WordPress code, for this reason Brain Monkey version of those functions returns `true` as well. But if you read _PHP Functions_ doc section or Mockery documentation you probably noticed a `andReturn` method that allows to force an expectation to return a given value. Once `expectAdded()` method works with Mockery expectations, you may be tempted to use it... if you do that **an exception will be thrown**. Copy // this expectation will thrown an error! Filters\expectAdded('the_title')->once()->andReturn(false); Reason is that if Brain Monkey had allowed a _mocked_ returning value for `add_action` and `add_filter` that had been in contrast with real WordPress code, with disastrous effects on tests. [PreviousSetup for WordPress testing](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-setup) [NextTest done hooks](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done) Last updated 4 years ago --- # Migration from v1 | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#updated-patchwork-version) \[Updated\] Patchwork Version ------------------------------------------------------------------------------------------------------------------------------------------ Patchwork has been updated to version 2. This new version allows to redefine PHP core functions and not only custom defined functions. (There are limitations, see [http://patchwork2.org/limitations/](http://patchwork2.org/limitations/) ). This new Patchwork version seems to also fix an annoying issue with undesired Patchwork cache. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#changed-setup-functions-breaking) \[Changed\] Setup Functions - BREAKING! ----------------------------------------------------------------------------------------------------------------------------------------------------------- On version 1 of Brain Monkey there where 4 static methods dedicated to setup: * `Brain\Monkey::setUp()` -> before each test that use only functions redefinition (no WP features) * `Brain\Monkey::tearDown()` -> after each test that use only functions redefinition (no WP features) * `Brain\Monkey::setUpWp()` -> before each test that use functions redefinition and WP features * `Brain\Monkey::tearDownWp()` -> after each test that use functions redefinition and WP features This has been simplified, in fact, **only two setup functions exists in Brain Monkey v2**: * `Brain\Monkey\setUp()` -> before each test that use functions redefinition and WP features * `Brain\Monkey\tearDown()` -> after each test, no matter if for functions redefinition or for also WP features Which means that for function redefinitions, only `Brain\Monkey\tearDown()` have to be called after each test, and nothing _before_ each test. To also use WP features, `Brain\Monkey\setUp()` have also to called before each test. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#changed-new-api-breaking) \[Changed\] New API - BREAKING! ------------------------------------------------------------------------------------------------------------------------------------------- Big part of Brain Monkey is acting as a "bridge" between Mockery an Patchwork, that is, make Mockery DSL for expectations available for functions and WordPress hooks. To access the Mockery API, Brain Monkey v1 provided two different methods: 1. using static methods on the `Brain\Monkey` class 2. using static methods on one of the three feature-specific classes `Brain\Monkey\Functions`, `Brain\Monkey\WP\Actions` or `Brain\Monkey\WP\Filters` For example: Copy // Brain Monkey v1 method one Brain\Monkey::functions::expect('some_function'); Brain\Monkey::actions()->expectAdded('init'); Brain\Monkey::filters()->expectApplied('the_title'); // Brain Monkey v1 method two Brain\Monkey\Functions::expect('some_function'); Brain\Monkey\WP\Actions::expectAdded('init'); Brain\Monkey\WP\Filters::expectApplied('the_title'); In Brain Monkey v2 there's only one method, that makes use of **functions**: Copy // Brain Monkey v2 Brain\Monkey\Functions\expect('some_function'); Brain\Monkey\Actions\expectAdded('init'); Brain\Monkey\Filters\expectApplied('the_title'); ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#renamed-method-for-done-actions) Renamed method for done actions For WordPress filters, there were in Brain Monkey v1 two methods: * `Filters::expectAdded()` * `Filters::expectApplied()` named after the WordPress functions `add_filter()` / `apply_filters()` But for actions there were: * `Actions::expectAdded()` * `Actions::expectFired()` `expectAdded()` pairs with `add_action()`, but `expectFired()` does not really pair with `do_action()`: this is why in Brain Monkey v2 **the method** `**expectFired()**` **has been replaced by the function** `**expectDone()**`. So, in version 2 there are total of 5 entry-point **functions** to Mockery API: * `Brain\Monkey\Functions\expect()` * `Brain\Monkey\Actions\expectAdded()` * `Brain\Monkey\Actions\expectDone()` * `Brain\Monkey\Filters\expectAdded()` * `Brain\Monkey\Filters\expectApplied()` [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#changed-default-expectations-behavior-breaking) \[Changed\] Default Expectations Behavior - BREAKING! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In Brain Monkey v1, expectation on the "times" an expected event happen was required. Copy class MyClass { public function doSomething() { return true; } } class MyClassTest extends MyTestCase { // this test passes in Brain Monkey v1 public function testSomething() { \Brain\Monkey\WP\Actions::expectAdded('init'); // this has pretty much no effect $class = new MyClass(); self::assertTrue($class->doSomething()); } } This **test passed in Brain Monkey v1**, because even if `Actions::expectAdded()` was used, the test does not fail unless something like `Actions::expectAdded('init')->once()` was used, which made the test pass only if `add_action( 'init' )` was called once. The reason is that Mockery default behavior is to add a `->zeroOrMoreTimes()` as expectation on number of times a method is called, so when the expectation is called _zero times_, that's a valid outcome. This was somehow confusing (because reading `expectAdded` one could _expect_ the test to fail if that thing did not happened), and also made tests unnecessarily verbose. **Brain Monkey v2, set Mockery expectation default to** `**->atLeast()->once()**` so, for example, the test above fails in Brain Monkey v2 if `MyClass::doSomething()` does not call `add_action('init')` at least once. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#changed-closure-string-representation-breaking) \[Changed\] Closure String Representation - BREAKING! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey allows to do some basic tests using `has_action()` / `has_filter()`, functions, to test if some portion of code have added some hooks. A "special" syntax, was already added in Brain Monkey v1 to permit the checking for hooks added using object instances as part of the hook callback, without having any reference to those objects. For example, assuming a function like: Copy namespace A\Name\Space; function test() { add_action('example_one', [new SomeClass(), 'aMethod']); add_action('example_two', function(array $foo) { /* ... */ }); } could be tested with in Brain Monkey v1 with: Copy // Brain Monkey v1: test(); self::assertNotFalse(has_action('example_one', 'A\Name\Space\SomeClass->aMethod()')); // pass self::assertNotFalse(has_action('example_two', 'function()')); // pass The syntax for string representation of callbacks including objects is unchanged in Brain Monkey v2, however, **the syntax for closures string representation has been changed to allow more fine grained control**. In fact, in Brain Monkey v1 _all_ the closures were represented as the string `"function()"`, in Brain Monkey v2 closure string representations also contain the parameters used in the closure signature: Copy // Brain Monkey v2: test(); self::assertNotFalse(has_action('example_one', 'A\Name\Space\SomeClass->aMethod()')); // pass self::assertNotFalse(has_action('example_two', 'function()')); // fail! self::assertNotFalse(has_action('example_two', 'function(array $foo)')); // pass! The closure string representation _does_ take into account: * name of the parameters * parameters type hints (works with PHP 7+ scalar type hints) * variadic arguments * `static` closures VS normal closures _does not_ take into account: * PHP 7 return type declaration * parameters defaults * content of the closure For example: Copy namespace A\Name\Space; $closure_1 = static function( array $foo, SomeClass $bar, int ...$ids ) : bool { /* */ } $closure_2 = function( array $foo, SomeClass $bar, array $ids = [] ) : bool { /* */ } // $closure_1 is represented as: "static function ( array $foo, A\Name\Space\SomeClass $bar, int ...$ids )"; // $closure_2 is represented as: "function ( array $foo, A\Name\Space\SomeClass $bar, array $ids )"; Note how type-hints using classes always have fully qualified names in string representation. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#changed-relaxed-callable-check) \[Changed\] Relaxed `callable` check ------------------------------------------------------------------------------------------------------------------------------------------------------ In Brain Monkey v1 methods and functions that accept a `callable` like, for example, second argument to `add_action()` / `add_filter()`, checked the received argument to be an actual callable PHP entity, using `is_callable`: Copy // this fail in Brain Monkey v1 if `SomeClass` was not available // or if SomeClass::aMethod would not be a valid method add_action( 'foo', [ SomeClass::class, 'aMethod' ] ); // this fail in Brain Monkey v1 if `Some\Name\Space\aFunction` is not available add_action( 'bar', 'Some\Name\Space\aFunction' ); For these reasons, it was often required to create a mock for unavailable classes or functions just to don't make Brain Monkey throw an exception, even if the mock was not used and not relevant for the test. Brain Monkey v2 is less strict on checking for `callable` and it accepts anything that _looks like_ a callable. Something like `[SomeClass::class, 'aMethod']` would be accepted even if `SomeClass` is not loaded at all, because _it looks like_ a callable. Same goes for `'Some\Name\Space\aFunction'`. However, something like `[SomeClass::class, 'a-Method']` or `[SomeClass::class, 'aMethod', 1]` or even `Some\Name\Space\a Function` will throw an exception because method and function names can't contain hyphens or spaces and when a callback is made of an array, it must have exactly two arguments. This more "relaxed" check allows to save creation of mocks that are not necessary for the logic of the test. It worth noting that when doing something like `[SomeClass::class, 'aMethod']` **if** the class `SomeClass` is available, Brain Monkey checks it to have an accessible method named `aMethod`, and raise an exception if not, but will not do any check if the class is not available. The same applies when object instances are used for callbacks, for example, using as callback argument `[$someObject, 'aMethod']`, the instance of `$someObject` is checked to have an accessible method named `aMethod`. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#fixed-apply_filters-default-behavior) \[Fixed\] `apply_filters` Default Behavior ------------------------------------------------------------------------------------------------------------------------------------------------------------------ The WordPress function `apply_filters()` is defined by Brain Monkey and it returns the first argument passed to it, just like WordPress: Copy self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass! In Brain Monkey v1 this was true _unless_ some expectation was added to the applied filter: Copy Brain\Monkey\WP\Filters::expectApplied('a_filter'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // fails in v1 **The test above fails in Brain Monkey v1**. The reason is that even if the expectation in first line is validated, it breaks the default `apply_filters` behavior, requiring the return value to be added to expectation to make the test pass again. For example, the following test used to pass in Brain Monkey v1: Copy Brain\Monkey\WP\Filters::expectApplied('a_filter')->andReturn('Foo'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass **In Brain Monkey v2 this is not necessary anymore.** Calling `expectApplied` on applied filters does **not** break the default behavior of `apply_filters` behavior, if no return expectations are added. The following test **passes in Brain Monkey v2**: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo', 'Bar'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass in v2! Please note that if any return expectation is added for a filter, return expectations must be added for all the set of arguments the filter might receive. For example: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo')->andReturn('Foo!'); Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Bar'); self::assertSame('Foo!', apply_filters('a_filter', 'Foo')); // pass self::assertSame('Bar', apply_filters('a_filter', 'Bar')); // fail! The second assertion fails because since we added a return expectation for the filter "'a_filter'" we need to add return expectation for \_all_ the possible arguments. This task is easier in Brain Monkey v2 thanks to the introduction of `andReturnFirstArg()` expectation method (more on this below). For example: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo')->andReturn('Foo!'); Brain\Monkey\Filters\expectApplied('a_filter')->zeroOrMoreTimes()->withAnyArgs()->andReturnFirstArg(); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass self::assertSame('Bar', apply_filters('a_filter', 'Bar')); // pass! `andReturnFirstArg()` used in combination with Mockery methods `zeroOrMoreTimes()->withAnyArgs()` allows to create a "catch all" behavior for filters when a return expectation has been added, without having to create specific expectations for each of the possible arguments a filter might receive. Of course, adding specific expectations for each of the possible arguments a filter might receive is still possible. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#added-utility-functions-stubs) \[Added\] Utility Functions Stubs -------------------------------------------------------------------------------------------------------------------------------------------------- There are WordPress functions that are often used in WordPress plugins or themes that are pretty much _logicless_, but still they need to be mocked in tests if WordPress is not available. Brain Monkey v2 now ships stubs for those functions, so it is not necessary to mock them anymore, they are: * `__return_true` * `__return_false` * `__return_null` * `__return_empty_array` * `__return_empty_string` * `__return_zero` * `trailingslashit` * `untrailingslashit` Those functions do exactly what they are expected to do, even if WordPress is not loaded: some functions mocking is now saved. Of course, their behavior can still be mocked, e.g. to make a test fail on purpose. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#added-support-for-doing_action-and-doing_filter) \[Added\] Support for `doing_action()` and `doing_filter()` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When adding expectation on returning value of filters, or when using `whenHappen` to respond to actions, inside the expectation callback, the function `current_filter()` in Brain Monkey v1 used to correctly resolve to the action / filter being executed. The functions `doing_action()` and `doing_filter()` didn't work: they were not provided at all with Brain Monkey v1 and required to be mocked "manually" . In Brain Monkey v2 those two functions are provided as well, and correctly return true or false when used inside the callbacks used to respond to hooks. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#added-method-andreturnfirstarg) \[Added\] Method `andReturnFirstArg()` -------------------------------------------------------------------------------------------------------------------------------------------------------- When adding expectations on returning value of applied filters or functions, it is now possible to use `andReturnFirstArg()` to make the Mockery expectations return first argument received. Copy // Brain\Monkey v2: Brain\Monkey\Functions\expect('foo')->andReturnFirstArg(); Brain\Monkey\Filters\expectApplied('the_title')->andReturnFirstArg(); // Brain\Monkey v1: Brain\Monkey\Functions\expect('foo')->andReturnUsing(function($arg) { return $arg; }); Brain\Monkey\Filters\expectApplied('the_title')->andReturnUsing(function($arg) { return $arg; }); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#added-method-andalsoexpectit) \[Added\] Method `andAlsoExpectIt()` ---------------------------------------------------------------------------------------------------------------------------------------------------- In Mockery, when creating expectations for multiple methods of same class, the method `getMock()` allows to do it without leaving "fluent interface chain", for example: Copy Mockery\mock(SomeClass::class) ->shouldReceive('exclamation')->with('Foo')->once()->andReturn('Foo!') ->getMock() ->shouldReceive('question')->with('Bar')->once()->andReturn('Bar?') ->getMock() ->shouldReceive('invert')->with('Baz')->once()->andReturn('zaB') The method `getMock()` is **not** available for Brain Monkey expectations. For this reason has been introduced `andAlsoExpectIt()`: Copy Brain\Monkey\Filters\expectApplied('some_filter') ->once()->with('Hello')->andReturn('Hello!') ->andAlsoExpectIt() ->atLeast()->twice()->with('Hi')->andReturn('Hi!') ->andAlsoExpectIt() ->zeroOrMoreTimes()->withAnyArgs()->andReturnFirstArg(); Of course, it also works in other kind of expectations, like for functions or for actions added or done. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more/migrating-from-v1#added-new-exceptions-classes) \[Added\] New Exceptions Classes ------------------------------------------------------------------------------------------------------------------------------------------------ In Brain Monkey v1, when exceptions were thrown, PHP core exception classes were used, like `\RuntimeException` or `\InvalidArgumentException`, and so on. In Brain Monkey v2, different custom exceptions classes have been added, to make very easy to catch any error thrown by Brain Monkey. Now, in fact, every exception thrown by Brain Monkey is of a custom type, and there's a hierarchy of exceptions classes for a total of 16 exception classes, all inheriting (one or more levels deep) the "base" exception class that is `Brain\Monkey\Exception`. [PreviousTest done hooks](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done) Last updated 5 years ago --- # Setup for functions testing | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#testing-framework-agnostic) Testing framework agnostic ----------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey can be used with any testing framework. Examples in this page will use PHPUnit, but the concepts are applicable at any testing framework. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#warning) Warning --------------------------------------------------------------------------------------------------- Brain Monkey uses [Patchwork](http://patchwork2.org/) to redefine functions. Brain Monkey 2.\* requires Patchwork 2 which allows to re-define both userland and core functions, with some [limitations](http://patchwork2.org/limitations/) . The main limitations that affects Brain Monkey are (from Patchwork website): * _Patchwork will fail on every attempt to redefine an internal function that is missing from the redefinable-internals array of your_ `_patchwork.json_`_._ * _Make sure that Patchwork is imported as early as possible, since any files imported earlier, including the one from which the importing takes place, will be missed by Patchwork's code preprocessor._ [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#setup-tests) Setup tests ----------------------------------------------------------------------------------------------------------- After Brain Monkey is part of the project (see _Getting Started / Installation_), to be able to use its features two simple steps are needed before being able to use Brain Monkey in tests: 1. be sure to require Composer autoload file _before_ running tests (e.g. PHPUnit users will probably require it in their bootstrap file). 2. call the function `Brain\Monkey\tearDown()` after any test ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#phpunit-example) PHPUnit example Let's take PHPUnit as example, the average test case class that uses Brain Monkey would be something like: Copy use PHPUnit_Framework_TestCase; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Brain\Monkey; class MyTestCase extends PHPUnit_Framework_TestCase { // Adds Mockery expectations to the PHPUnit assertions count. use MockeryPHPUnitIntegration; protected function tearDown() { Monkey\tearDown(); parent::tearDown(); } } After that for all test classes can extend this class instead of directly extending `PHPUnit_Framework_TestCase`. That's all. Again, I used PHPUnit for the example, but any testing framework can be used. For function mocking and testing there are two entry-point functions: * `**Functions\when()**` * `**Functions\expect()**` See dedicated documentation pages. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#namespaced-functions) Namespaced functions ----------------------------------------------------------------------------------------------------------------------------- All the code examples in this documentation make use of functions in global namespace. However, note that namespaced functions are supported as well, just be sure to pass the fully qualified name of the functions: Copy Functions\expect('a_global_function'); Functions\expect('My\\App\\awesome_function'); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools#note-for-wordpressers) Note for WordPressers ------------------------------------------------------------------------------------------------------------------------------- Anything said in this page is fine for WordPress functions too, they are PHP functions, after all. However, Brain Monkey has specific features for WordPress, and there is a way to setup tests for **all** Brain Monkey features (WordPress-specific and not). **If you want to use Brain Monkey to test code wrote for WordPress, it is preferable to use the setup explained in the** _**"WordPress / Setup"**_ **section that** _**includes**_ **the setup needed to use Brain Monkey tools for functions.** [PreviousInstallation](https://giuseppe-mazzapica.gitbook.io/brain-monkey/general/installation) [NextPatching functions with when()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when) Last updated 6 years ago --- # Why bother | Brain Monkey Just to be clear, Brain Monkey is useful for testing code wrote _for_ WordPress (plugin, themes) not WordPress core. More specifically, it is useful to run **unit tests**. Integration tests or end-to-end tests are a thing: you need to be sure that your code works good _with_ WordPress. But **unit** tests are meant to be run **without loading WordPress environment**. Every component that is unit tested, should be tested in isolation: when you test a class, you only have to test that specific class, assuming all other code (e.g. WordPress code) is working perfectly. This is not only because doing that tests will run much faster, but also because the key concept in unit testing is that every piece of code should work _per se_, in this way if a test fails there is only one possible culprit. By assuming all the external code is working perfectly, it is possible to test the behavior of the SUT (System Under Test), without any _interference_. To deepen these concepts, read [this answer](https://wordpress.stackexchange.com/a/164138/35541) I wrote for WordPress Development (StackExchange) site, that also contains some tips to write better _testable_ WordPress code. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools#if-wordpress-is-not-loaded) If WordPress is not loaded... --------------------------------------------------------------------------------------------------------------------------------------------- WordPress functions are not available, and trying to run tests in that situation, tests fail with fatal errors. Unless you use Brain Monkey. It allows to mock WordPress function (just like any PHP function), and to check how they are called inside your code. See the _PHP Function_ documentation section for a deep explanation on how it works. Moreover, among others, WordPress [Plugin API functions](https://codex.wordpress.org/Plugin_API) are particularly important and a very fine grained control on how they are used in code is pivotal to proper test WordPress extensions. This is why Brain Monkey comes with a set of features specifically designed for that. [PreviousTesting functions with expect()](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-expect) [NextWordPress testing tools](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-tools) Last updated 6 years ago --- # Installation | Brain Monkey To install Brain Monkey you need: * PHP 5.6+ * [Composer](https://getcomposer.org/) Brain Monkey is available on Packagist, so the only thing you need to do is to add it as a dependency for your project. That can be done by running following command in your project folder: Copy composer require brain/monkey:2.* --dev As alternative you can directly edit your `composer.json` by adding: Copy { "require-dev": { "brain/monkey": "~2.0.0" } } I've used `require-dev` because, being a testing tool, Brain Monkey should **not** be included in production. Brain Monkey can work with any testing framework, so it doesn't require any of them. To run your tests you'll probably need to require a testing framework too, e.g. [PHPUnit](https://phpunit.de/) or [phpspec](https://www.phpspec.net/en/latest/) . [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/general#dependencies) Dependencies --------------------------------------------------------------------------------------------- Brain Monkey needs 2 libraries to work: * [Mockery](http://docs.mockery.io/en/latest/) (BSD-3-Clause) * [Patchwork](http://patchwork2.org/) (MIT) They will be installed for you by Composer. When installed in development mode (to test itself), Brain Monkey also requires: * [PHPUnit](https://phpunit.de/) (MIT) [PreviousIntroduction](https://giuseppe-mazzapica.gitbook.io/brain-monkey) [NextSetup for functions testing](https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-setup) Last updated 6 years ago --- # Migration from v1 | Brain Monkey [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#updated-patchwork-version) \[Updated\] Patchwork Version ------------------------------------------------------------------------------------------------------------------------ Patchwork has been updated to version 2. This new version allows to redefine PHP core functions and not only custom defined functions. (There are limitations, see [http://patchwork2.org/limitations/](http://patchwork2.org/limitations/) ). This new Patchwork version seems to also fix an annoying issue with undesired Patchwork cache. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#changed-setup-functions-breaking) \[Changed\] Setup Functions - BREAKING! ----------------------------------------------------------------------------------------------------------------------------------------- On version 1 of Brain Monkey there where 4 static methods dedicated to setup: * `Brain\Monkey::setUp()` -> before each test that use only functions redefinition (no WP features) * `Brain\Monkey::tearDown()` -> after each test that use only functions redefinition (no WP features) * `Brain\Monkey::setUpWp()` -> before each test that use functions redefinition and WP features * `Brain\Monkey::tearDownWp()` -> after each test that use functions redefinition and WP features This has been simplified, in fact, **only two setup functions exists in Brain Monkey v2**: * `Brain\Monkey\setUp()` -> before each test that use functions redefinition and WP features * `Brain\Monkey\tearDown()` -> after each test, no matter if for functions redefinition or for also WP features Which means that for function redefinitions, only `Brain\Monkey\tearDown()` have to be called after each test, and nothing _before_ each test. To also use WP features, `Brain\Monkey\setUp()` have also to called before each test. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#changed-new-api-breaking) \[Changed\] New API - BREAKING! ------------------------------------------------------------------------------------------------------------------------- Big part of Brain Monkey is acting as a "bridge" between Mockery an Patchwork, that is, make Mockery DSL for expectations available for functions and WordPress hooks. To access the Mockery API, Brain Monkey v1 provided two different methods: 1. using static methods on the `Brain\Monkey` class 2. using static methods on one of the three feature-specific classes `Brain\Monkey\Functions`, `Brain\Monkey\WP\Actions` or `Brain\Monkey\WP\Filters` For example: Copy // Brain Monkey v1 method one Brain\Monkey::functions::expect('some_function'); Brain\Monkey::actions()->expectAdded('init'); Brain\Monkey::filters()->expectApplied('the_title'); // Brain Monkey v1 method two Brain\Monkey\Functions::expect('some_function'); Brain\Monkey\WP\Actions::expectAdded('init'); Brain\Monkey\WP\Filters::expectApplied('the_title'); In Brain Monkey v2 there's only one method, that makes use of **functions**: Copy // Brain Monkey v2 Brain\Monkey\Functions\expect('some_function'); Brain\Monkey\Actions\expectAdded('init'); Brain\Monkey\Filters\expectApplied('the_title'); ### [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#renamed-method-for-done-actions) Renamed method for done actions For WordPress filters, there were in Brain Monkey v1 two methods: * `Filters::expectAdded()` * `Filters::expectApplied()` named after the WordPress functions `add_filter()` / `apply_filters()` But for actions there were: * `Actions::expectAdded()` * `Actions::expectFired()` `expectAdded()` pairs with `add_action()`, but `expectFired()` does not really pair with `do_action()`: this is why in Brain Monkey v2 **the method** `**expectFired()**` **has been replaced by the function** `**expectDone()**`. So, in version 2 there are total of 5 entry-point **functions** to Mockery API: * `Brain\Monkey\Functions\expect()` * `Brain\Monkey\Actions\expectAdded()` * `Brain\Monkey\Actions\expectDone()` * `Brain\Monkey\Filters\expectAdded()` * `Brain\Monkey\Filters\expectApplied()` [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#changed-default-expectations-behavior-breaking) \[Changed\] Default Expectations Behavior - BREAKING! --------------------------------------------------------------------------------------------------------------------------------------------------------------------- In Brain Monkey v1, expectation on the "times" an expected event happen was required. Copy class MyClass { public function doSomething() { return true; } } class MyClassTest extends MyTestCase { // this test passes in Brain Monkey v1 public function testSomething() { \Brain\Monkey\WP\Actions::expectAdded('init'); // this has pretty much no effect $class = new MyClass(); self::assertTrue($class->doSomething()); } } This **test passed in Brain Monkey v1**, because even if `Actions::expectAdded()` was used, the test does not fail unless something like `Actions::expectAdded('init')->once()` was used, which made the test pass only if `add_action( 'init' )` was called once. The reason is that Mockery default behavior is to add a `->zeroOrMoreTimes()` as expectation on number of times a method is called, so when the expectation is called _zero times_, that's a valid outcome. This was somehow confusing (because reading `expectAdded` one could _expect_ the test to fail if that thing did not happened), and also made tests unnecessarily verbose. **Brain Monkey v2, set Mockery expectation default to** `**->atLeast()->once()**` so, for example, the test above fails in Brain Monkey v2 if `MyClass::doSomething()` does not call `add_action('init')` at least once. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#changed-closure-string-representation-breaking) \[Changed\] Closure String Representation - BREAKING! --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Brain Monkey allows to do some basic tests using `has_action()` / `has_filter()`, functions, to test if some portion of code have added some hooks. A "special" syntax, was already added in Brain Monkey v1 to permit the checking for hooks added using object instances as part of the hook callback, without having any reference to those objects. For example, assuming a function like: Copy namespace A\Name\Space; function test() { add_action('example_one', [new SomeClass(), 'aMethod']); add_action('example_two', function(array $foo) { /* ... */ }); } could be tested with in Brain Monkey v1 with: Copy // Brain Monkey v1: test(); self::assertNotFalse(has_action('example_one', 'A\Name\Space\SomeClass->aMethod()')); // pass self::assertNotFalse(has_action('example_two', 'function()')); // pass The syntax for string representation of callbacks including objects is unchanged in Brain Monkey v2, however, **the syntax for closures string representation has been changed to allow more fine grained control**. In fact, in Brain Monkey v1 _all_ the closures were represented as the string `"function()"`, in Brain Monkey v2 closure string representations also contain the parameters used in the closure signature: Copy // Brain Monkey v2: test(); self::assertNotFalse(has_action('example_one', 'A\Name\Space\SomeClass->aMethod()')); // pass self::assertNotFalse(has_action('example_two', 'function()')); // fail! self::assertNotFalse(has_action('example_two', 'function(array $foo)')); // pass! The closure string representation _does_ take into account: * name of the parameters * parameters type hints (works with PHP 7+ scalar type hints) * variadic arguments * `static` closures VS normal closures _does not_ take into account: * PHP 7 return type declaration * parameters defaults * content of the closure For example: Copy namespace A\Name\Space; $closure_1 = static function( array $foo, SomeClass $bar, int ...$ids ) : bool { /* */ } $closure_2 = function( array $foo, SomeClass $bar, array $ids = [] ) : bool { /* */ } // $closure_1 is represented as: "static function ( array $foo, A\Name\Space\SomeClass $bar, int ...$ids )"; // $closure_2 is represented as: "function ( array $foo, A\Name\Space\SomeClass $bar, array $ids )"; Note how type-hints using classes always have fully qualified names in string representation. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#changed-relaxed-callable-check) \[Changed\] Relaxed `callable` check ------------------------------------------------------------------------------------------------------------------------------------ In Brain Monkey v1 methods and functions that accept a `callable` like, for example, second argument to `add_action()` / `add_filter()`, checked the received argument to be an actual callable PHP entity, using `is_callable`: Copy // this fail in Brain Monkey v1 if `SomeClass` was not available // or if SomeClass::aMethod would not be a valid method add_action( 'foo', [ SomeClass::class, 'aMethod' ] ); // this fail in Brain Monkey v1 if `Some\Name\Space\aFunction` is not available add_action( 'bar', 'Some\Name\Space\aFunction' ); For these reasons, it was often required to create a mock for unavailable classes or functions just to don't make Brain Monkey throw an exception, even if the mock was not used and not relevant for the test. Brain Monkey v2 is less strict on checking for `callable` and it accepts anything that _looks like_ a callable. Something like `[SomeClass::class, 'aMethod']` would be accepted even if `SomeClass` is not loaded at all, because _it looks like_ a callable. Same goes for `'Some\Name\Space\aFunction'`. However, something like `[SomeClass::class, 'a-Method']` or `[SomeClass::class, 'aMethod', 1]` or even `Some\Name\Space\a Function` will throw an exception because method and function names can't contain hyphens or spaces and when a callback is made of an array, it must have exactly two arguments. This more "relaxed" check allows to save creation of mocks that are not necessary for the logic of the test. It worth noting that when doing something like `[SomeClass::class, 'aMethod']` **if** the class `SomeClass` is available, Brain Monkey checks it to have an accessible method named `aMethod`, and raise an exception if not, but will not do any check if the class is not available. The same applies when object instances are used for callbacks, for example, using as callback argument `[$someObject, 'aMethod']`, the instance of `$someObject` is checked to have an accessible method named `aMethod`. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#fixed-apply_filters-default-behavior) \[Fixed\] `apply_filters` Default Behavior ------------------------------------------------------------------------------------------------------------------------------------------------ The WordPress function `apply_filters()` is defined by Brain Monkey and it returns the first argument passed to it, just like WordPress: Copy self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass! In Brain Monkey v1 this was true _unless_ some expectation was added to the applied filter: Copy Brain\Monkey\WP\Filters::expectApplied('a_filter'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // fails in v1 **The test above fails in Brain Monkey v1**. The reason is that even if the expectation in first line is validated, it breaks the default `apply_filters` behavior, requiring the return value to be added to expectation to make the test pass again. For example, the following test used to pass in Brain Monkey v1: Copy Brain\Monkey\WP\Filters::expectApplied('a_filter')->andReturn('Foo'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass **In Brain Monkey v2 this is not necessary anymore.** Calling `expectApplied` on applied filters does **not** break the default behavior of `apply_filters` behavior, if no return expectations are added. The following test **passes in Brain Monkey v2**: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo', 'Bar'); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass in v2! Please note that if any return expectation is added for a filter, return expectations must be added for all the set of arguments the filter might receive. For example: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo')->andReturn('Foo!'); Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Bar'); self::assertSame('Foo!', apply_filters('a_filter', 'Foo')); // pass self::assertSame('Bar', apply_filters('a_filter', 'Bar')); // fail! The second assertion fails because since we added a return expectation for the filter "'a_filter'" we need to add return expectation for \_all_ the possible arguments. This task is easier in Brain Monkey v2 thanks to the introduction of `andReturnFirstArg()` expectation method (more on this below). For example: Copy Brain\Monkey\Filters\expectApplied('a_filter')->once()->with('Foo')->andReturn('Foo!'); Brain\Monkey\Filters\expectApplied('a_filter')->zeroOrMoreTimes()->withAnyArgs()->andReturnFirstArg(); self::assertSame('Foo', apply_filters('a_filter', 'Foo', 'Bar')); // pass self::assertSame('Bar', apply_filters('a_filter', 'Bar')); // pass! `andReturnFirstArg()` used in combination with Mockery methods `zeroOrMoreTimes()->withAnyArgs()` allows to create a "catch all" behavior for filters when a return expectation has been added, without having to create specific expectations for each of the possible arguments a filter might receive. Of course, adding specific expectations for each of the possible arguments a filter might receive is still possible. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#added-utility-functions-stubs) \[Added\] Utility Functions Stubs -------------------------------------------------------------------------------------------------------------------------------- There are WordPress functions that are often used in WordPress plugins or themes that are pretty much _logicless_, but still they need to be mocked in tests if WordPress is not available. Brain Monkey v2 now ships stubs for those functions, so it is not necessary to mock them anymore, they are: * `__return_true` * `__return_false` * `__return_null` * `__return_empty_array` * `__return_empty_string` * `__return_zero` * `trailingslashit` * `untrailingslashit` Those functions do exactly what they are expected to do, even if WordPress is not loaded: some functions mocking is now saved. Of course, their behavior can still be mocked, e.g. to make a test fail on purpose. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#added-support-for-doing_action-and-doing_filter) \[Added\] Support for `doing_action()` and `doing_filter()` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When adding expectation on returning value of filters, or when using `whenHappen` to respond to actions, inside the expectation callback, the function `current_filter()` in Brain Monkey v1 used to correctly resolve to the action / filter being executed. The functions `doing_action()` and `doing_filter()` didn't work: they were not provided at all with Brain Monkey v1 and required to be mocked "manually" . In Brain Monkey v2 those two functions are provided as well, and correctly return true or false when used inside the callbacks used to respond to hooks. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#added-method-andreturnfirstarg) \[Added\] Method `andReturnFirstArg()` -------------------------------------------------------------------------------------------------------------------------------------- When adding expectations on returning value of applied filters or functions, it is now possible to use `andReturnFirstArg()` to make the Mockery expectations return first argument received. Copy // Brain\Monkey v2: Brain\Monkey\Functions\expect('foo')->andReturnFirstArg(); Brain\Monkey\Filters\expectApplied('the_title')->andReturnFirstArg(); // Brain\Monkey v1: Brain\Monkey\Functions\expect('foo')->andReturnUsing(function($arg) { return $arg; }); Brain\Monkey\Filters\expectApplied('the_title')->andReturnUsing(function($arg) { return $arg; }); [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#added-method-andalsoexpectit) \[Added\] Method `andAlsoExpectIt()` ---------------------------------------------------------------------------------------------------------------------------------- In Mockery, when creating expectations for multiple methods of same class, the method `getMock()` allows to do it without leaving "fluent interface chain", for example: Copy Mockery\mock(SomeClass::class) ->shouldReceive('exclamation')->with('Foo')->once()->andReturn('Foo!') ->getMock() ->shouldReceive('question')->with('Bar')->once()->andReturn('Bar?') ->getMock() ->shouldReceive('invert')->with('Baz')->once()->andReturn('zaB') The method `getMock()` is **not** available for Brain Monkey expectations. For this reason has been introduced `andAlsoExpectIt()`: Copy Brain\Monkey\Filters\expectApplied('some_filter') ->once()->with('Hello')->andReturn('Hello!') ->andAlsoExpectIt() ->atLeast()->twice()->with('Hi')->andReturn('Hi!') ->andAlsoExpectIt() ->zeroOrMoreTimes()->withAnyArgs()->andReturnFirstArg(); Of course, it also works in other kind of expectations, like for functions or for actions added or done. [](https://giuseppe-mazzapica.gitbook.io/brain-monkey/more#added-new-exceptions-classes) \[Added\] New Exceptions Classes ------------------------------------------------------------------------------------------------------------------------------ In Brain Monkey v1, when exceptions were thrown, PHP core exception classes were used, like `\RuntimeException` or `\InvalidArgumentException`, and so on. In Brain Monkey v2, different custom exceptions classes have been added, to make very easy to catch any error thrown by Brain Monkey. Now, in fact, every exception thrown by Brain Monkey is of a custom type, and there's a hierarchy of exceptions classes for a total of 16 exception classes, all inheriting (one or more levels deep) the "base" exception class that is `Brain\Monkey\Exception`. [PreviousTest done hooks](https://giuseppe-mazzapica.gitbook.io/brain-monkey/wordpress-specific-tools/wordpress-hooks-done) Last updated 5 years ago ---