10up / wp_mock

WordPress API Mocking Framework
https://wp-mock.gitbook.io
Other
674 stars 69 forks source link

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

Closed acobster closed 7 years ago

acobster commented 8 years ago

Hey folks. Thank you for this great framework!

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

<?php

require_once 'vendor/autoload.php';

use PHPUnit\Framework\TestCase;

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

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

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

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

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

Running this test outputs this error:

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

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

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

Any ideas?

ericmann commented 8 years ago

That's because add_action is defined by the framework itself, so your mocked version is never called ...

acobster commented 8 years ago

I don't understand. It's defined by the framework before I even call WP_Mock::wpFunction()? What's the rationale there? Why is it different than say, get_post, as in the README examples?

ericmann commented 8 years ago

It's defined by the framework to support the ::expectFilter and ::onAction and :: expectActionAdded family of functions.

Note the section in the readme specifically about mocking actions and filters: https://github.com/10up/wp_mock#mocking-actions-and-filters

acobster commented 7 years ago

Thanks, somehow I didn't connect that.

If I replace the WP_Mock::wpFunction(...) call with WP_Mock::expectActionAdded('pre_get_posts', \WP_Mock\Functions::type('callable'));, it gives me:

Method intercepted() from Mockery_1__intercept should be called at least 1 times but called 0 times.

Looks like the intercepted method on the internal mock is never called. It seems like the code that's causing this is in the WP_Mock::expectHookAdded method. That sets up a HookedCallbackResponder instance with a callback value of [$intercept, 'intercepted'] -- the method that WP_Mock seems to be expecting. However, the callback is never actually called.

I can get it to work by duplicating the expectHookAdded code in my test, and adding a call to react(). Obviously this isn't ideal:

  public function testAddAdminFilter() {
    $intercept = \Mockery::mock( 'intercept' );
    $intercept->shouldReceive( 'intercepted' )->atLeast()->once();

    $callback = function() {};

    /** @var WP_Mock\HookedCallbackResponder $responder */
    $responder = WP_Mock::onHookAdded( 'pre_get_posts', WP_Mock\Functions::type('callable') )
      ->with( $callback, 10, 1 );
    $responder->perform( array( $intercept, 'intercepted' ) );
    $responder->react();

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

Am I missing something else here? Seems like in the normal use-case you'd want to always call the intercept-callback, but again maybe I'm just not understanding the intention. :)

Thanks again!

acobster commented 7 years ago

Hey there, sorry for harping on this thread. Original issue was obviously my mistake, closing this one out!