brain-wp / brainmonkey Goto Github PK
View Code? Open in Web Editor NEWMocking utility for PHP functions and WordPress plugin API
Home Page: https://giuseppe-mazzapica.gitbook.io/brain-monkey/
License: MIT License
Mocking utility for PHP functions and WordPress plugin API
Home Page: https://giuseppe-mazzapica.gitbook.io/brain-monkey/
License: MIT License
I can't find a 2.5.0
tag here in the repo, but Packagist seems to think there is one and is serving it when running composer update
.
Just thought you should know as this may cause problems in the future if a 2.4.3
release would be tagged.
I am testing a method, used as an ajax callback, which has a validation check inside.
The validation part inside the method looks like this
/*
* Validation check.
*/
if ( ! isset( $_POST['myKey'] ) ) {
$response = 'Some error'; // Originally I have `esc_html__` here which is mocked.
\wp_die( \wp_json_encode( $response ) ); // wp_json_encode is aliased with json_encode.
}
In my test I've mocked the wp_die function like this
Functions\when('wp_die')->alias(function($value) {
return $value;
});
But the test fails because it seems that the execution of the callback method goes on (nonce verification and other functionality).
I'm puzzled as to why this happens.
Since my wp_die should return the first argument that is passed to it (\wp_json_encode( $response )
), which should exit the method, and the test should pass. When I turn the coverage report, the part that checks if it's set (validation), and the wp_die
is green, but then the rest fails, because I have an Undefined index: myKey
(triggered by the nonce check).
Is there some extra functionality I'm missing in the userFunction?
This code in a static method under test:
add_action('save_post', __CLASS__ . "::on_save_post", 10, 2);
Causes the following to be thrown:
''dirtsimple\imposer\PostModel::on_save_post'' is not a valid function name.
at /repos/imposer/vendor/brain/monkey/src/Name/Exception/InvalidName.php:80
#0 Brain\Monkey\Name\Exception\InvalidName::createFor (/repos/imposer/vendor/brain/monkey/src/Name/Exception/InvalidName.php:32)
#1 Brain\Monkey\Name\Exception\InvalidName::forFunction (/repos/imposer/vendor/brain/monkey/src/Name/FunctionName.php:93)
#2 Brain\Monkey\Name\FunctionName->parseName (/repos/imposer/vendor/brain/monkey/src/Name/FunctionName.php:39)
#3 Brain\Monkey\Name\FunctionName->__construct (/repos/imposer/vendor/brain/monkey/src/Name/CallbackStringForm.php:132)
#4 Brain\Monkey\Name\CallbackStringForm->parseString (/repos/imposer/vendor/brain/monkey/src/Name/CallbackStringForm.php:73)
#5 Brain\Monkey\Name\CallbackStringForm->parseCallback (/repos/imposer/vendor/brain/monkey/src/Name/CallbackStringForm.php:39)
#6 Brain\Monkey\Name\CallbackStringForm->__construct (/repos/imposer/vendor/brain/monkey/src/Hook/HookStorage.php:227)
#7 Brain\Monkey\Hook\HookStorage->parseArgsToAdd (/repos/imposer/vendor/brain/monkey/src/Hook/HookStorage.php:163)
#8 Brain\Monkey\Hook\HookStorage->pushToStorage (/repos/imposer/vendor/brain/monkey/src/Hook/HookStorage.php:58)
#9 Brain\Monkey\Hook\HookStorage->pushToAdded (/repos/imposer/vendor/brain/monkey/inc/wp-hook-functions.php:21)
#10 add_action (/repos/imposer/src/PostModel.php:27)
#11 dirtsimple\imposer\PostModel::configure (/repos/imposer/specs/PostModel.spec.php:111)
The issue appears to be that CallbackStringForm->parseString
doesn't think that static method string callbacks such as 'dirtsimple\imposer\PostModel::on_save_post'
are valid Wordpress filters or actions. Changing the code under test to use the array form of static method specification works, but the issue should be fixed in BrainMonkey.
I'm going through the documentation, and I can see examples for tests that use add_filter
and add_action
, but I don't see any mention of testing remove_filter/action
.
So am I right in assuming this is the right way to test for remove_filter
in a method?
class My_Class {
public function set_allowed_rest_headers() : void {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE, PATCH', false );
...
}
}
}
Test:
class Rest_Tests extends InitTestCase {
public function setUp() {
parent::setUp();
$this->test_class = new My_Class();
}
public function test_allowed_rest_headers() {
$this->portal_functionality->set_allowed_rest_headers();
self::assertFalse( has_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ) );
}
}
The InitTestCase
looks like this
use PHPUnit\Framework\TestCase;
use Brain\Monkey;
class InitTestCase extends TestCase {
/**
* Setup method necessary for Brain Monkey to function
*/
protected function setUp() {
parent::setUp();
Monkey\setUp();
}
/**
* Teardown method necessary for Brain Monkey to function
*/
protected function tearDown() {
Monkey\tearDown();
parent::tearDown();
}
}
The test passes, so I guess this is ok? Also I'm testing the added filter by adding
self::assertTrue( has_filter( 'rest_pre_serve_request', 'function( $value )' ) );
and this also passes, but I don't see a green bar over that line in my code coverage report.
EDIT
I added
Filters\expectAdded( 'rest_pre_serve_request' )->once()->whenHappen( function ( $value ) {
$value;
});
$this->portal_functionality->set_allowed_rest_headers();
static::assertFalse( has_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ) );
static::assertTrue( has_filter( 'rest_pre_serve_request', 'function( $value )' ) );
To my test function and still my second filter isn't covered. Also, how do you test anonymous functions?
Patchwork 2.1.0 has been released, but Brain Monkey is limited to 2.0.*
.
Current documentation says
Currently, Brain Monkey supports PHP from 5.4.x to 5.6.x.
from https://brain-wp.github.io/BrainMonkey/docs/what-and-why.html
I'm running in PHP 7.0.2
, any objections to changing the documentation from 5.6.x
to 7.0.x
?
I'm happy to write the PR.
When I run this code:
expect( 'func_1' )
->once()
->with( 'krya' )
->andReturn( 'krya' );
$this->assertSame( 'krya', func_1( 'ne-krya' ) );
Result: Failed asserting that null is identical to 'krya'.
. It looks well.
But when I run this code:
expect( 'func_1' )
->with( 'krya' )
->once()
->andReturn( 'krya' );
$this->assertSame( 'krya', func_1( 'ne-krya' ) );
OK (1 test, 1 assertion)
Works but why?
At the moment, whenHappen
is only allowed for actions (added and expected).
Once added filters (on the countrary of expected filters) can't return any value, whenHappen
should be allowed in that case as well.
I've got an issue with mocking internal functions (which seems to be supported by the underlying patchwork library) in conjunction with phpunit.
I've added gzencode
as an overridable internal to patchwork.json
and set up the expectation via Functions\expect
. This works fine until I try to generate coversge information, when the return value is not the expected false
but null
, which lets the test case bomb out.
In tests there is the mocked function d()
.
This causes issues with Patchwork... for reasons I can't sill understand.
I tried changing the name of the function and everything works.
BrainMonkey does not seem to correctly identify dynamically created callback methods.
I created a new callback method to be used as an add_filter()
callback:
class MyClass() {
public function register( $arg ) {
$method_name = "callback_for_${arg}";
$this->$method_name = function ( $value ) use ( $arg ) {
return $value;
}
add_filter( 'my_filter', [ $this, $method_name ] );
}
}
This code will cause BrainMonkey to throw an InvalidArgumentException:
InvalidArgumentException: A callback is required to add a filter.
When I var_dump the state inside of the Brain\Monkey\WP\Hooks::args()
method, I get the following:
array(5) {
'args' =>
array(0) {
}
'type' =>
string(6) "filter"
'getId' =>
bool(false)
'hook' =>
string(21) "my_filter"
'callback' =>
array(2) {
[0] =>
class MyClass#29 (2) {
public $callback_for_test =>
class Closure#50 (3) {
...
}
}
[1] =>
string(17) "callback_for_test"
}
}
Brain monkey uses Patchwork to mock functions behavior.
However, Patchwork needs a function is defined to be able to replace it.
This is why when Brain Monkey is asked to mock a unexistent function it first defines it on the fly, just creating a function with no body (so a do-nothing function that returns nothing).
After that, Brain Monkey uses Patchwork to replace it with a mock object provided by Mockery.
So far, so good.
Problem is that on subsequent tests, since they all run in same PHP process (normally) the function stay defined.
This is not a problem in majority of cases, because either:
In first case, the function is not used, so is irrelevant.
In second case, the test is very probably going to fail (unless the function is mocked again) because we are replacing a function that does something, with a function that does nothing and return nothing.
However, there might be cases of functions that return nothing in their real definition and might not be possible to distinguish from the mocked version.
This is why I think is better when we define the mocked version of an undefined function, to define a function that triggers an error. This way if the function is not used by SUT nothing bad happen. If it used by SUT and mocked in tests, nothing bad happen again. However, if it used in SUT and not replaced in tests the error will be triggered as one might expect.
I am developing a plugin, and part of the plugin functionality uses WP_User to interact with the WordPress Database. I want to mock that functionality, and the documentation seems to suggest that it is possible using stubs :
https://brain-wp.github.io/BrainMonkey/docs/functions-stubs.html
Functions\stubs([
'is_user_logged_in' => true,
'current_user_can' => true,
'wp_get_current_user' => function() {
return \Mockery::mock('\WP_User');
}
]);
this code - when inserted into my project throws the following error :
Error: Call to undefined function Brain\Monkey\Functions\stubs()
Here's my unit test:
class Deprecated_Test extends TestCase {
/**
* Test Genesis contributors array.
*
* @group contributors
* @expectedDeprecated genesis_contributors
*/
public function test_genesis_contributors() {
$contributors = genesis_contributors();
$this->assertGreaterThan( 1, count( $contributors ), 'If there are not at least two contributors for a Genesis cycle, something is wrong.' );
// Loop through and perform tests on each contributor.
array_walk( $contributors, function ( $c ) {
$this->assertTrue( is_array( $c ), 'One of the contributors is not an array' );
$this->assertArrayHasKey( 'name', $c, 'One of the contributors lacks a name' );
$this->assertArrayHasKey( 'url', $c, 'One of the contributors lacks a URL' );
$this->assertArrayHasKey( 'gravatar', $c, 'One of the contributors lacks a Gravatar' );
$this->assertRegExp( '#^https?://#', $c['url'], 'A contributor URL looks wrong' );
$this->assertRegExp( '#^https://0\.gravatar\.com/avatar/[a-f0-9]{32}\?s=120#', $c['gravatar'], 'A contributor Gravatar URL is malformed' );
} );
}
}
When TestCase
extends WP_UnitTestCase
(i.e. testing in an WordPress environment), the test passes.
When TestCase
extends PHPUnit_Framework_TestCase
(i.e. not in a WordPress environment), the test fails. This is due to the WordPress handling of the @expectedDeprecated
annotation.
Is this something that BrainMonkey could handle as well?
In order to use BrainMonkey with PHPUnit I have to create a custom test case class that calls the tearDown()
function for each and every project.
Like Mockery does, BrainMonkey could provide these class itself: Brain\Monkey\Integration\PHPUnit\TestCase
:
namespace Brain\Monkey\Integration\PhpUnit;
class TestCase extends \PhpUnit\Framework\TestCase
{
public function tearDown(): void
{
Brain\Monkey\tearDown();
parent::tearDown();
}
}
And in order to integrate with Mockery itself: Brain\Monkey\Integration\PHPUnit\MockeryTestCase
:
namespace Brain\Monkey\Integration\PhpUnit;
class MockeryTestCase extends \Mockery\Adapter\Phpunit\MockeryTestCase
{
public function tearDown(): void
{
Brain\Monkey\tearDown();
parent::tearDown();
}
}
This would reduce the redundancy of these test case classes a lot. Another benefit would be to use these classes in your IDE test class templates.
Hi again.
I have a weird situation here: in a plugin I defined Monkey as dev dependency like so:
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"ShortcodeReplace\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ShortcodeReplace\\Test\\Helper\\": "tests/Helper/"
}
},
"require-dev": {
"php": ">=5.4.0",
"brain/monkey": "~1.2"
}
When I installed the dependencies in the past, I got Monkey at 1.2.1 and Patchwork at 1.4.0.
Today I wanted to update the dependencies:
david@vaio:/var/www/.../wp-content/plugins/edit-post-shortcode-replace$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing antecedent/patchwork (1.4.0)
- Installing antecedent/patchwork (1.3.5)
Loading from cache
- Removing brain/monkey (1.2.1)
- Installing brain/monkey (1.3.2)
Loading from cache
Writing lock file
Generating autoload files
Now when I'm trying to run the tests, I see this:
$ phpunit
PHPUnit 4.8.23 by Sebastian Bergmann and contributors.
Fatal error: Call to undefined function Patchwork\replace() in /var/www/projects/inpsyde/mi-cockpit-ae/wp-content/plugins/edit-post-shortcode-replace/vendor/brain/monkey/src/Monkey/Functions.php on line 77
My phpunit bootstrap file is just vendor/autoload.php
. The problem still persists after I cleared the composer cache.
Any ideas, what this is about?
Native WP has_filter()
/has_action()
can be called without arguments to check if anything is attached to a hook.
Monkey implementation doesn't allow this and insist on checking for a specific callback. This makes it impossible to assert hook has nothing attached to it.
My use case: I temporarily override time zone option by hooking before calling WP API function and unhooking after. The more thorough/broad assertion is to check that I have no leftovers of any kind hooked.
Check for specific callback is possible but it's more prone to error (for example if I make mistake in my assertion and check for nonโexisting callback it will always pass).
// works
$this->assertFalse( has_filter( 'pre_option_timezone_string', 'DateTimeZone->getName()', 10, 0 ) );
// InvalidArgumentException: A callback is required to add a filter.
$this->assertFalse( has_filter( 'pre_option_timezone_string' ) );
Following functions are missing from the Functions\stubTranslationFunctions()
:
_n()
_nx()
_n_noop()
_nx_noop()
P.S. Happy to send PR, if the suggestion looks good.
P.P.S. Documentations seems to be the out of the sync.
What am I doing wrong here?
class Example
{
public function __construct()
{
add_filter('template_include', [$this, 'template']);
}
public function template(string $template): string
{
return 'something';
}
}
class ExampleTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Brain\Monkey\setUp();
}
protected function tearDown(): void
{
Brain\Monkey\tearDown();
parent::tearDown();
}
public function testExample()
{
Brain\Monkey\Filters\expectAdded('template_include')
->with('Example->template()');
new Example;
}
}
Returns
There was 1 error:
1) ExampleTest::testExample
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_0::add_filter_template_include([0 => object(Example), 1 => 'template'], 10, 1). Either the method was unexpected or its arguments matched no expected argument list for this method
/.../vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:92
/.../vendor/brain/monkey/src/Hook/HookExpectationExecutor.php:127
/.../vendor/brain/monkey/src/Hook/HookExpectationExecutor.php:64
/.../vendor/brain/monkey/inc/wp-hook-functions.php:35
/.../src/Example.php:24 (add_filter('template_include', [$this, 'template']))
/.../tests/ExampleTest.php:50 (new Example)
The sidebar links are hidden behind a display: none
, which makes them invisible to screen reader users, and inaccessible when there is no JavaScript available.
Suggestion:
no-js
body class by default to all pages, and add a small JS immediately after the opening body
tag which changes it to js
.no-js
class to conditionally apply styles visibly hide the square orange button that toggles the whole sidebar, since it won't work when no JS is available.I tried using
Functions\when('My_Plugin\Helpers\General_Helper::get_array_value')->justReturn('value');
For mocking static method in my unit test, but I get an error
Brain\Monkey\Name\Exception\InvalidName: ''My_Plugin\Helpers\General_Helper::get_array_value'' is not a valid function name.
Is there a way to mock static methods with Brain Monkey?
Apologies if this is mentioned in the documentation, I've looked but couldn't see anything that would help. Given the following code, how would I go about testing that the code within the anonymous function?
public function register(): void
{
add_action('init', function () {
// Register the post type.
register_post_type($this->postType, $this->arguments);
// Add the columns
if(isset($this->columns)) {
$this->columns->register();
}
});
}
I've tried the following, the assertTrue works fine, but the test fails at expect('register_post_type')->once();
.
expect('register_post_type')->once();
$this->createInstance()->register();
$this->assertTrue(has_action('init', 'function ()'));
I have code I'm testing that includes
$message = __( 'Hello', 'ironcode' );
which is throwing
Error: Call to undefined function
A fake function that returns the first value (i.e. no translation occurs), would be helpful.
Also see PRs #55, #57, #61 and #62.
I'd like to suggest expanding the automated CI/QA checks done via Travis to prevent the above mentioned issues and/or get earlier warning about them.
Suggestions for things to add to the Travis build:
expect( 'func_1' )
->once()
->with(
[
'arg1' => Mockery::type( 'string' ),
]
)
->andReturn( true );
$this->assertTrue( func_1( [ 'arg1' => 'krya' ] ) );
Mockery::type( ... )
works only for first level of arguments.
Hi!
Most plugins would somehow internationalize human-readable strings. It would be great therefore if this tool had at least some of the i18n functions of WordPress.
I would be super fine with just a stub of __()
that returns the first argument unchanged.
Of course, it would be better if we could set some expectations as well.
I've not done any investigation as to why, but I've managed to produce a minimal working example which demonstrates the problem.
The following is quite contrived but consider the following test case which has two tests. The first test fails (as is expected), but in doing so, it causes the second to also fail: When the second is run in isolation it passes, when it is run after the failing test it also fails.
It fails because the value being returned is null: it's as though Monkey forgets the return value after a failure.
<?php
use Brain\Monkey;
class blogFunctionsTest extends PHPUnit_Framework_TestCase {
public function setUp() {
parent::setUp();
Monkey::setUpWP();
}
public function tearDown() {
Monkey::tearDownWP();
parent::tearDown();
}
public function testA() {
Monkey::functions()->expect('get_blog_option')
->once()
->with( 53, 'blogname' )
->andReturn( 'Mr Bloggy' );
$this->assertEquals( 'Mr Bloggy', \get_blog_option( 53, 'blogname' ) );
//This assertion should trigger an error, failing the test
$this->assertEquals( 'Mr Bloggy', \get_blog_option( 53, 'blogname' ) );
}
public function testB() {
Monkey::functions()->expect('get_blog_option')
->zeroOrMoreTimes()
->with( 53, 'blogname' )
->andReturn( 'Mr Bloggy' );
//These assertions should pass...
$this->assertEquals( 'Mr Bloggy', \get_blog_option( 53, 'blogname' ) );
$this->assertEquals( 'Mr Bloggy', \get_blog_option( 53, 'blogname' ) );
}
}
phpunit --filter=blogFunctionsTest
Result:
There was 1 error:
1) blogFunctionsTest::testA
Mockery\Exception\InvalidCountException: Method get_blog_option(53, "blogname") from Mockery_0__get_blog_option should be called
exactly 1 times but called 2 times.
...
--
There was 1 failure:
1) blogFunctionsTest::testB
Failed asserting that null matches expected 'Mr Bloggy'.
FAILURES!
Tests: 2, Assertions: 3, Failures: 1, Errors: 1.
Running just the second test:
phpunit --filter=blogFunctionsTest::testB
Result:
OK (1 test, 2 assertions)
testA()
- the second test fails (as we would expect) but not because it is invoking a function twice which should only be invoked once but because the return value is null
.Hi,
I am new to BrainMonkey and I am just hitting a problem within my test script. I am using this code below to test WordPress function get_option().
Functions\expect('get_option')->once()->andReturnNull();
Once I run my test, It will show the error:
1) Javagee\OptionTest::test_construct
Mockery\Exception\InvalidCountException: Method get_option(<Any Arguments>) from Mockery_0 should be called
exactly 1 times but called 0 times.
If I used this code, my test script will pass.
Functions\when('get_option')->justReturn(true);
I noticed that method get_option is not being called for Function\expect.
My Setup below:
"require-dev": {
"phpunit/phpunit": "7.2.*",
"spatie/phpunit-snapshot-assertions": "2.0.*",
"antecedent/patchwork": "^2.0"
},
"require": {
"php": ">=5.6",
"brain/monkey": "2.4.0",
"composer/installers": "~1.0",
"mmamedov/page-cache": "^2.0"
},
Any advise or to point me in the right direction will very much appreciated.
Thanks in advanced.
Gerard
Normally has_filter
returns the priority of the filter if it finds one (https://developer.wordpress.org/reference/functions/has_filter/). But this library only returns bool
. Perhaps it is not super important, but here's an example of where testing the correct priority would be good.
class Example
{
protected int $priority;
public function __construct(int $priority = 10)
{
$this->priority = $priority;
}
public function register(): void
{
add_filter('something', [$this, 'something'], $this->priority);
}
public function something(): string
{
return 'whatever';
}
}
class ExampleTest extends TestCase
{
public function testPriority(): void
{
$priority = 50;
$instance = (new Example($priority))->register();
$this->assertSame(
$priority,
has_filter('something', [$instance, 'something']),
);
}
}
I just noticed that is_wp_error()
and absint
are not documented at
https://brain-wp.github.io/BrainMonkey/docs/wordpress-tools.html, only in https://github.com/Brain-WP/BrainMonkey/blob/master/docs/wordpress-tools.md.
In this version we are allowed to modify internal functions, which I would need to accomplish my task.
Will you update the dependencies for the project?
PR: #21
As a simple case, I've got:
add_filter( 'foo', function() {
return '';
});
in a method.
When I call that method in my test, my assertion is:
static::assertTrue( has_filter( 'foo', 'function ()' ) );
...as this is how the docs say to do it.
The test fails however with a simple Failed asserting that false is true.
I've got another test in the same file, also using has_filter()
, which suitably passes or fails when detecting a class method call.
What's the trick here?
BrainMonkey 1.4.2
The actual code I want to test is:
foreach ( $this->config->getArrayCopy() as $key => $value ) {
add_filter( "genesis_pre_get_option_{$key}", function () use ( $value ) {
return $value;
} );
}
Hi,
I have set up a test case following the documentation at https://brain-wp.github.io/BrainMonkey/docs/wordpress-hooks-done.html#test-fired-hooks-with-expectations - my test case is almost identical to the example provided in docs.
I noticed that when the test fails, there's an error reported, but when it passes, PHPUnit (I'm using version 6.5.7) complains about it being risky: "This test did not perform any assertions".
Is this expected behavior?
I've noticed that there currently is no Filters\expectRemoved
function as a counterpart to Filters\expectAdded
. Is there a technical reason or is this simply an oversight? I think providing such functions (for filters and actions) would make the API more complete.
Hello comrades! I have one issue with mocking my functions writen in wordpress theme. For generate base test files i utilize the wp scraffold
after i add download BrainMonkey by Composer and add to my bootstrap.php
import Composer's autoload:
// Require Brain\Monkey
require_once __DIR__ . '/libs/vendor/autoload.php';
So then i include namespaces in my tests file:
use PHPUnit\Framework\TestCase;
use Brain\Monkey\Functions;
use Brain\Monkey;
Then i add setUp()
and tearDown()
methods:
function setUp() {
parent::setUp();
Monkey\setUp();
}
function tearDown() {
Monkey\tearDown();
parent::tearDown();
}
Then i add Mockery trait adapter to test class:
use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
Write test for my function mocking another my function:
function test_func_is_user_banned_ban_empty() {
Functions\expect( 'dirt_get_bans_by_player' )->once()->with( 'user id' )->andReturn( null );
$no_ban_fields_found = dirt_is_user_banned( 'user id' );
$this->assertSame( null, $no_ban_fields_found );
}
And after i run PHPUnit tests i have that issue:
1) Ban_Tests::test_func_is_user_banned_ban_empty
Patchwork\Exceptions\DefinedTooEarly: The file that defines dirt_get_bans_by_player() was included earlier than Patchwork. This is likely a result of an improper setup; see readme for details.
Help me please, how fix it. Im little confused because i import autoload.php
with BrainMonkey by firtst instruction in bootstrap.php
. Thanks a lot.
Sometimes, it is necessary to test that when a filter is applied, it actually gives the correct result. In these cases, the SUT will often add the filter, which may be a closure. It is not possible to make a functional test of such a SUT.
Contrary to the documentation, mocked functions like add_filter()
and apply_filters()
don't actually work in the way their WP equivalents do. Examples:
Note that the returning value of those functions (most of the times) will work out of the box as you might expect.
Yet again, Brain Monkey, when possible, tries to make WordPress functions it redefines behave in the same way of real WordPress functions.
In reality, when calling apply_filters()
from the test, callbacks added with add_filter()
, e.g. from the SUT, don't actually run. Thus, the functional test may be completely impossible without duplicating some of the code in the SUT.
I see two possible ways of addressing this:
Running the following:
\Brain\Monkey\Filters\expectApplied('some_filter')->once()->with('arg1', 1);
apply_filters('some_filter','arg1',1)
Yields:
Mockery\Exception\InvalidCountException: Method apply_filters_some_filter('arg1', 1) from Mockery_1 should be called
exactly 1 times but called 0 times.
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery\CountValidator\Exact.php:38
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery\Expectation.php:310
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery\ExpectationDirector.php:119
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery\Container.php:303
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery\Container.php:288
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\mockery\mockery\library\Mockery.php:204
C:\Users\qwerty\AppData\Local\Temp\wordpress-tests-lib\vendor\brain\monkey\inc\api.php:38
C:\xampp72\htdocs\mca\wp-content\plugins\etsy_woocommerce\tests\EtsyProductTest.php:29
My setup:
phpunit 7.5.11
php 7.2.18
Hi,
In my wordpress plugin that I am trying to unit test with BrainMonkey, i have classes which registers shortcodes with the wordpress API
`class RaceResultShortcode {
function __construct() {
add_shortcode('bhaa_race_results' ,array($this,'bhaa_race_results_shortcode'));
add_shortcode('bhaa_race_title' ,array($this,'bhaa_race_title_shortcode'));
}`
in my Test, i see this error where the 'add_shortcode()' is an undefined function
Error : Call to undefined function BHAA\core\race\add_shortcode() C:\emeraldjava\bhaa_wordpress_plugin\src\core\race\RaceResultShortcode.php:14 C:\emeraldjava\bhaa_wordpress_plugin\src\Main.php:65 C:\emeraldjava\bhaa_wordpress_plugin\tests\MainTest.php:13
Is there a suggested workaround for this scenario?
Thanks in advance.
Paul
Just wondering if there is interest in the addition of more mocks for some typically, often mocked, WP translation/output escaping functions, as well as some miscellaneous WP functions.
The functions I'm thinking of are:
__()
etc - to just return the original string.Functions\stubTranslationFunctions()
method, so never mind about this.esc_attr()
, esc_html()
etc - also to just return the original stringFunctions\stubEscapeFunctions()
method, so never mind about this.wp_json_encode()
- return the output of json_encode()
without further sanity check.wp_slash()
- same functionality as WPwp_unslash()
- same functionality as WPuser_trailingslashit()
just trailingslashit()
wp_parse_args()
and by extension wp_parse_str()
- same functionality as WPPlease let me know if any (or all) of these peek your interest.
did_action()
is mocked by Brain Monkey to mimic the WordPress behavior.
However, when the action is called with characters that are ouside of the regex class [a-zA-Z0-9_]
Brain Monkey fails to recognize the action has been called.
For example:
public function testDidActionWorks()
{
do_action('foo');
do_action('foo.bar');
$didFoo = did_action('foo');
$didFooBar = did_action('foo.bar');
self::assertCount(1, $didFoo); // works
self::assertCount(1, $didFooBar ); // fail
}
At the moment is not possible to mock same function with different arguments.
For example following code does not work as expected:
Functions::expect('function_name')
->with(true)
->once()
->andReturn('First');
Functions::expect('function_name')
->with(false)
->once()
->andReturn('Second');
If function_name()
is called in the SUT two times, once with true
and once with false
test fails because of errors on Mockery argument expectations.
Hello, it's me again. Do you remember our conversation here? I found some time to search for the problem and it looks like Monkey::functions()
does work like expected but Monkey::action()->expectFired()
does not.
In fact I would expect the same behavior described in #5.
I wrote a test in 83c80e7, following the FunctionsTest::testSameFunctionDifferentArguments()
, that shows the problem:
There was 1 error:
1) Brain\Monkey\Tests\WP\ActionFireTest::testFiredSameActionDifferentArguments
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_14__do_double_action::do_action_double_action("arg_1"). Either the method was unexpected or its arguments matched no expected argument list for this method
/var/www/projects/php/brain-monkey/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:93
/var/www/projects/php/brain-monkey/src/Monkey/WP/Hooks.php:249
/var/www/projects/php/brain-monkey/src/Monkey/WP/Actions.php:93
/var/www/projects/php/brain-monkey/src/Monkey/WP/Hooks.php:110
/var/www/projects/php/brain-monkey/inc/wp-functions.php:43
/var/www/projects/php/brain-monkey/tests/unit/WP/ActionFireTest.php:181
I'm not completely in to Mockery/Monkey to say what's the problem, but I'll take some time to figure it out.
I wanted to create a test suit for Wordpress which includes lucatume/WP-Browser, BrainMonkey and Robo. I put them in the same composer vendor. Robo is a tool for executing commands, when I tried to test a tiny command in Robo, this error occured. And I tested some other commands but this error appeared after each execution. The strange thing is, I just didn't use any fonction of BrainMonkey. Here is the screenshot for the error :
Then I tried to remove dependencies, when I removed BrainMonkey 2.0.2, everything worked fine. I also tried BrainMonkey 1.5.0 and this one works fine.
Here are the dependencies of my composer.json file:
I really want to use BrainMonkey 2.0, please help me out, thank you.
In the plugin main file I have this
<?php
namespace My_Plugin;
require __DIR__ . '/vendor/autoload.php';
function activate() {
Includes\Activator::activate();
}
register_activation_hook( __FILE__, __NAMESPACE__ . '\\activate' );
function init_plugin() {
$plugin = new Includes\Main();
$plugin->run();
}
init_plugin();
I'm kinda following the plugin boilerplate to an extent. Running phpunit makes require, the inside of activate() function, register_activation_hook and init_plugin red (untested).
But since they are outside any class, I'm not 100% sure how to test them :S
Especially require
, since that is a language construct.
Should I use Filters\expectApplied
on the register_activation_hook
?
When mocking an hook that has special chars in names, e.g.
Filters::expectApplied( 'foo.bar' );
Test result is a parse error.
Reason is that internally Brain Monkey creates a Mockery mock using hook id, and Mockery creates a class using hook name as class name.
So, if the hook name is not valid as class name a parse error occurs.
(...unless that expectation also specifies a return value)
The documentation states that apply_filters
will return the filtered argument. This is true, unless you add an expectation to it:
Filters::expectApplied('bar')->once();
$foo = apply_filters('foo', 'Yes!');
$bar = apply_filters('bar', 'Yes!');
$this->assertEquals( 'Yes!', $foo ); //Passes
$this->assertEquals( 'Yes!', $bar ); //Fails - asserting NULL equals expected 'Yes!'
With an expectation you must provide a return value. For example:
Filters::expectApplied('bar')->once()->andReturnUsing( function() {
return func_get_arg(0);
} );
$filtered = apply_filters('bar', 'Yes!');
$this->assertEquals( 'Yes!', $filtered );
Perhaps I just misread the documentation, but I think the documentation should highlight this limitation.
I have looked into fixing this, as you can use byDefault()
to specify default behaviour but:
To mimic apply_filters()
you need to use returnUsing()
to create a closure to return the filtered argument. Unfortunately you cannot over-ride that with a returnValue()
even if using by byDefault()
//Default behaviour
$mock->returnUsing(function(){
return func_get_arg(0);
})->byDefault();
//Can't be over-ridden by client code
$mock->returnValue( 'foo' );
//Mock will **not** return 'foo'.
I think this limitation is due to the fact mockery doesn't let you mix returnValue()
and returnUsing()
.
Even if we could work around (1), byDefault()
can only be over-ridden once. So testApplySameFilterDifferentArguments
fails.
This is more a question on the right use of expect
than an issue.
I'm trying to test that a method was called, but I'm not sure I'm doing it right.
I have a dependency class with a purge method
namespace Vendor\Name;
class Dependency {
public function purge( $url ) {}
}
And another class using this dependency and method in a clean_domain
method
namespace Vendor\Name;
class Handler {
public function __construct( Dependency $dependency ) {
$this->dependency = $dependency;
}
public function clean_domain( $url ) {
$this->dependency->purge( $url );
}
}
In my tests I have the following test method
public function testShouldPurge() {
$dependency = $this->createMock('Vendor\Name\Dependency');
$handler = new Handler( $dependency );
Functions\expect( 'Vendor\\Name\\purge' )
->times(1)
->withAnyArgs();
$handler->clean_domain( 'http://example.org' );
}
But am getting the error Mockery\Exception\InvalidCountException: Method Vendor_Name_Purge (<Any Arguments>) from Mockery_0 should be called exactly 1 times but called 0 times.
when running the tests.
What am I missing here?
This is probably more of a user error or opportunity for documentation/example updates than issue with BrainMonkey code itself.
In both of the scenarios below I encounter an error about call to undefined function for add_action.
plugin-dir/logging.php
<?php
add_action( 'http_api_debug', 'verify_http_api_debug', 10, 5 );
function verify_http_api_debug( $response, $type, $class, $args, $url ) {
if ( WP_ENV == 'development' ) {
error_log( 'HTTP_API_DEBUG - Request URL: ' . var_export( $url, true ) );
}
}
plugin-dir/tests/logging-Test.php
<?php
use Brain\Monkey\Filters;
use Brain\Monkey\Actions;
use Brain\Monkey\Functions;
class ExampleLoggingTest extends MonkeyTestCase {
public function test_verify_http_api_debug() {
self::assertSame( 10, has_action( 'http_api_debug', 'verify_http_api_debug' ) );
}
}
Actual error
Call to undefined function add_action()
plugin-dir/logging.php
<?php
namespace Example\WP\MUPlugins\Example_Core;
class ExampleCoreLogging {
static $instance = false;
public static function get_instance () {
if ( ! self::$instance ) {
self::$instance = new self;
}
return self::$instance;
}
public function __construct() {
add_action( 'http_api_debug', [ __CLASS__, 'verify_http_api_debug' ], 10, 5 );
}
static function verify_http_api_debug( $response, $type, $class, $args, $url ) {
if ( WP_ENV == 'development' ) {
error_log( 'HTTP_API_DEBUG - Request URL: ' . var_export( $url, true ) );
}
}
}
if ( class_exists( 'Example\WP\MUPlugins\Example_Core\ExampleCoreLogging' ) ) {
$ExampleCoreLogging= \Example\WP\MUPlugins\Example_Core\ExampleCoreLogging::get_instance();
}
plugin-dir/tests/logging-Test.php
<?php
namespace Example\WP\MUPlugins\Example_Core;
use Brain\Monkey\Filters;
use Brain\Monkey\Actions;
use Brain\Monkey\Functions;
class ExampleCoreLogging extends MonkeyTestCase {
public function test_verify_http_api_debug() {
self::assertSame( 10, has_action( 'http_api_debug', [ 'Example\WP\MUPlugins\Example_Core\ExampleCoreLogging', 'verify_http_api_debug' ] ) );
}
}
Actual error
Fatal error: Uncaught Error: Call to undefined function Example\WP\MUPlugins\Example_Core\add_action()
Other Notes
The closest I've been able to get either scenario to work is:
I'm rewriting my plugin and trying to use patterns like factory and service locator like here: https://github.com/schlessera/wcbtn-2018-api (factory, plugin).
I have set up my testing, loaded everything using composer (I'm autoloading my classes using classmap), and when I run tests, I can confirm that the unit test is passing through the code properly.
For instance, I want to check the hooks defined in register
method that looks like this:
/**
* Register the plugin with the WordPress system.
*
* @throws Exception\InvalidService If a service is not valid.
*/
public function register() : void {
add_action( 'plugins_loaded', [ $this, 'register_services' ] );
add_action( 'init', [ $this, 'register_assets_handler' ] );
}
I tried adding the print_r
inside this method and I can see some output when I run
./vendor/bin/phpunit -c phpunit.xml.dist --no-coverage --colors=always"
My test looks like this:
<?php
/**
* Class Plugin tests
*
* @package My_Plugin\Tests\Unit\Src
*/
use Brain\Monkey\Actions;
use My_Plugin\Tests\Init_Test_Case;
use My_Plugin\Core\Plugin_Factory;
use My_Plugin\Core\Plugin;
/**
* Class that tests the Main plugin functionality.
*/
class Plugin_Factory_Test extends Init_Test_Case {
public function setUp() {
parent::setUp();
Plugin_Factory::create()->register();
}
public function test_plugin_execution() {
$this->assertTrue( has_action( 'plugins_loaded', 'My_Plugin\Core\Plugin->register_services()' ) );
$this->assertTrue( has_action( 'init', 'My_Plugin\Core\Plugin->register_assets_handler()' ) );
}
}
When the plugin is loaded the Plugin_Factory::create()->register();
method is called, so this is what I've put in my tests setUp
method.
As I've said, this works, as print_r
will show stuff in my terminal.
But I get
1) Plugin_Factory_Test::test_plugin_execution
Failed asserting that false is true.
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/tests/unit/src/test-plugin-factory.php:31
I even tried with
Actions\expectAdded( 'plugins_loaded' );
Actions\expectAdded( 'init' );
But that throws the following error:
There was 1 error:
1) Plugin_Factory_Test::test_plugin_execution
Mockery\Exception\InvalidCountException: Method add_action_plugins_loaded(<Any Arguments>) from Mockery_0 should be called
at least 1 times but called 0 times.
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery/CountValidator/AtLeast.php:47
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery/Expectation.php:310
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:123
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery/Container.php:303
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery/Container.php:288
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/mockery/mockery/library/Mockery.php:204
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/vendor/brain/monkey/inc/api.php:38
/Users/denis/vagrant-local/www/personal/project/public_html/wp-content/plugins/my-plugin/tests/init-setup.php:28
Any idea why this doesn't want to work?
Before refactoring (wasn't using factory and other design patterns besides dependency injection), this worked (I had one issue opened before with rest_pre_serve_request
iirc).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.