Giter Site home page Giter Site logo

brain-wp / brainmonkey Goto Github PK

View Code? Open in Web Editor NEW
288.0 7.0 30.0 1.18 MB

Mocking utility for PHP functions and WordPress plugin API

Home Page: https://giuseppe-mazzapica.gitbook.io/brain-monkey/

License: MIT License

PHP 100.00%
wordpress mockery php mockey-patching phpunit tests

brainmonkey's People

Contributors

d3f3kt avatar dependabot[bot] avatar dnaber-de avatar garyjones avatar gmazzap avatar idealien avatar jrfnl avatar paulshryock avatar pospjan avatar rarst avatar shvlv avatar smoothie avatar tfrommen avatar widoz avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

brainmonkey's Issues

Mocking wp_die doesn't return and exit the method under test

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?

add_action('some_action', 'SomeClass::someStaticMethod') throws InvalidName

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.

Remove filter test example?

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.

screen shot 2018-08-21 at 12 07 20

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?

Why expect function works unexpectedly?

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?

whenHappen() should be allowed for added filters

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.

Trouble mocking internal function (gzencode)

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.

Issue in test with d() function

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.

Issue with dynamically created callback method

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"
  }
}

Undefined function definition persist single 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:

  • the subsequent tests do not use the function
  • the subsequent tests do use it

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.

Unsure how to test / mock WP_User

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()

Support @expectDeprecated?

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?

Provide test case classes for PHPUnit

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.

Error: `Call to undefined function Patchwork\replace()` after composer update

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?

has_filter()/has_action() should also work without callback argument

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' ) );

Add plural translation functions to the stubs list

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.

Expectation on "times" required for `withArgs`

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)

Docs: Add better support for when no JS is available

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:

  • Add a 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.
  • Use this class to conditionally apply styles which can visibly hide the page links until expanded.
  • Use the 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.

Mocking static methods?

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?

How to set expectations for inside anonymous functions

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 ()'));

Add fake translation 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.

Improve CI/QA process

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:

  • Linting the code against all supported PHP versions.
  • An automated scan of the code with PHPCompatibility
  • Security check of the Composer dependencies.
  • Validation of the Composer file.

Mocks for deep arguments in the function doesn't work

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.

Missing i18n functions

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.

Failing test can impact other tests

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' ) );
    }

}

Summary:

 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)

Other observations

  • It appears to be tied to the function which is not invoked correctly. I.e. if I change the function name in the second test, it does not fail, but changing the arguments as no effect (the second test still fails).
  • As earlier mentioned, the order matters (but that is to be expected).
  • If I run an identical copy of 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.

Expects not functioning

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

has_filter/has_action return true instead of the hook priority

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']),
    );
  }
}

Patchwork 2.0

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

Detecting closure for add_filter

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;
	} );
}

"This test did not perform any assertions" warning when using Actions\expectDone in test

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?

There should be an expectRemoved function for Filters and Actions

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.

Issue with mocking my functions (Wordpress)

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.

WordPress hook functions don't behave as expected

The Problem

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.

Possible Cause

Contrary to the documentation, mocked functions like add_filter() and apply_filters() don't actually work in the way their WP equivalents do. Examples:

Example 1

Note that the returning value of those functions (most of the times) will work out of the box as you might expect.

Example 2

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.

Suggested Solution

I see two possible ways of addressing this:

  1. Make these mocked functions optionally behave like their WP counterparts, i.e. run the actual callbacks and return results.
  2. Explicitly mention in the documentation that they will not run the added callbacks.

Expect Applied not working

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

How to mock the WordPress `add_shortcode()` function?

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

Tag a new release

Would be great if a new release could be tagged so people can start testing their own code properly for compatibility with PHP 7.4, without being bogged down by errors coming from the BrainMonkey framework.

Related #55, #61

WP: adding mocks for some miscellaneous functions

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. Duh, just discovered the Functions\stubTranslationFunctions() method, so never mind about this.
  • esc_attr(), esc_html() etc - also to just return the original string Duh, just discovered the Functions\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 WP
  • wp_unslash() - same functionality as WP
  • user_trailingslashit() just trailingslashit()
  • wp_parse_args() and by extension wp_parse_str() - same functionality as WP

Please let me know if any (or all) of these peek your interest.

`did_action` / `Filters::applied()` fail if actions / filters contains special characters

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
}

Allow mocking same functions with different arguments

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.

Allow mocking same _actions_ with different arguments

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.

ERROR: fseek() stream does not support seeking while not using BrainMonkey

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 :
image
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:
image

I really want to use BrainMonkey 2.0, please help me out, thank you.

Test plugin main file (outside of a class)?

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?

Parse error when hook to mock is contains special chars.

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.

`apply_filters` returns null if the filter has an expectation

(...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:

  1. 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().

  2. Even if we could work around (1), byDefault() can only be over-ridden once. So testApplySameFilterDifferentArguments fails.

How to use Brain Monkey to mock class methods?

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?

Testing w/o class or class instantiation within plugin

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.

Scenario 1 - Equivalent of functions.php

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()

Scenario 2 - OOP/Class based

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:

  • Remove the class instantiation from the plugin file
  • Add it to the test_verify_http_api_debug function
  • The unit test will pass, but use of the plugin within WP does not call/execute expected hook.
  • The MonkeyTestCase is identical to your docs example for WP in both cases for MonkeyClass.php
  • The bootstrap.php file requires the MonkeyClass, vendor\autoload.php and the plugin file.

Testing hooks from plugin factory fails

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).

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

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

  • D3 photo D3

    Data-Driven Documents codes.