Skip to content

PHPMock::defineFunctionMock not working? #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
krodyrobi opened this issue Dec 29, 2016 · 14 comments
Closed

PHPMock::defineFunctionMock not working? #12

krodyrobi opened this issue Dec 29, 2016 · 14 comments

Comments

@krodyrobi
Copy link

So I've read about the namespace bug and applied the recommended fix but it doesn't seem to work in this instance.
It did in another case but not here, even tried to run solo / in a different process but to no avail.
Added all the functions to the defineFunctionMock just to be safe even though most are not needed.

I've ran out of ideas, any thoughts on why this might not work?

<?php

namespace presslabs\minify;

use phpmock\phpunit\PHPMock;


class Test_PL_Minify extends \PHPUnit_Framework_TestCase {
  use PHPMock;

  public static function setUpBeforeClass() {
    # these are needed because of https://bugs.php.net/bug.php?id=68541
    # in short we use a call from an unaltered namespace then alter it which leads php confused
    PHPMock::defineFunctionMock( __NAMESPACE__, 'true_constant' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'defined' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'constant' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'pl_minify_page' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'minify_html' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'minify_css' );
    PHPMock::defineFunctionMock( __NAMESPACE__, 'minify_js' );
    # TODO refactor this duplication
  }

  function test_minify_page_minification() {
    $true_constant = $this->getFunctionMock( __NAMESPACE__, 'true_constant' );
    $true_constant->expects( $this->once() )
            ->with( 'PL_MINIFY_HTML' )
            ->willReturn( true );

    $input = 'random';

    $minify_html = $this->getFunctionMock( __NAMESPACE__, 'minify_html' );
    $minify_html->expects( $this->once() )
          ->with( $input )
          ->willReturn( 'mini' );


    $actual = pl_minify_page( $input );

    $this->assertSame( 'mini', $actual );
  }
}
namespace presslabs\minify;


function pl_minify_page( $buffer ) {
  error_log('before if' . serialize($buffer));
  if ( true_constant( 'PL_MINIFY_HTML' ) ) {
    error_log( "a" . serialize( $buffer ) );
                # gets here

    return minify_html( $buffer );
  }
  error_log('not modified');
  return $buffer;
}


function minify_css( $text ) {
  require_once( PL_PLUGINS_LIB . '/class-cssmin.php' );
  $compressor = new \CSSmin();
  $contents   = $compressor->run( $text );

  return $contents;
}


function minify_js( $text ) {
  require_once( PL_PLUGINS_LIB . '/class-JavaScriptPacker.php' );

  $packer   = new \JavaScriptPacker( $text, 'None', true, false );
  $contents = $packer->pack();

  return $contents;
}


function minify_html( $text ) {
  require_once( PL_PLUGINS_LIB . '/class-minify-html.php' );

  $buffer = \Minify_HTML::minify( $text, array(
    'cssMinifier'       => true_constant( 'PL_MINIFY_INLINE_CSS' ) ? __NAMESPACE__ . '\\minify_css' : null,
    'jsMinifier'        => true_constant( 'PL_MINIFY_INLINE_JS' ) ? __NAMESPACE__ . '\\minify_js' : null,
    'jsCleanComments'   => true_constant( 'PL_MINIFY_REMOVE_COMMENTS' ),
    'htmlCleanComments' => true_constant( 'PL_MINIFY_REMOVE_COMMENTS' )
  ) );

  return $buffer;
}


function true_constant( $name ) {
  return defined( $name ) and constant( $name ) === 'True';
}
@krodyrobi krodyrobi changed the title Mocks not working even with PHPMock::defineFunctionMock PHPMock::defineFunctionMock not working? Dec 29, 2016
@krodyrobi
Copy link
Author

If i try an call the mocked function inside the test it works.
Is there a way to check if the function I'm calling in the tested function is the actual mock? Or better yet, what could cause the mock not to be applied.

I've tried requiring files prior to the test running and it seems to mock it as well.
I'm not entirely sure the bug in PHP affects my code as the functions I'm mocking are used outside of a class scope, but reading through the comments I've reached something that could prove me wrong

namespace fred;
some_function_which_executes_builtin();
include "some_include_in_namespace_fred_which overrides_same_builtin.php";
some_function_which_executes_builtin();

Seems to have a wider scope functions / class constants etc.

@malkusch
Copy link
Member

malkusch commented Jan 3, 2017

Is there a way to check if the function I'm calling in the tested function is the actual mock?

Sure, use a debugger and have a look into which function it jumps.

what could cause the mock not to be applied.

Any code which uses the function previously in the same namespace. PHP is caching namespace resolution. If you're running a test suite and any test before is using said function unmocked, you're out of luck within the same process. You can confirm that by running your tests under HHVM. They might pass there, as HHVM doesn't have this bug.

@runInSeparateProcess is a very effective way to prevent such conditions. Did you run your tests with that annotation?

@krodyrobi
Copy link
Author

I've tried the annotation and the problem still persists, this makes me think that some of the imports have side-effects and call the method in question before the tests even run :|.

Unfortunately using HHVM for testing isn't optimal as production code does not run on it.

Edit: I've commented out all files except the one containing the sources for the methods in question and still the issue is present. Even went further and changed the bootstrap file to a bare minimum and still nothing.

@malkusch
Copy link
Member

malkusch commented Jan 3, 2017

Could you provide a SSCCE so that I could reproduce it?

@krodyrobi
Copy link
Author

krodyrobi commented Jan 3, 2017

ssce.zip

It is currently using the mockery extension but the issue is the same with the simple php-mock-phpunit.
The import location does not seem to matter. If you have any ideas on what I could check further I'll gladly help.

I ran it with php 5.6.29.

@krodyrobi
Copy link
Author

krodyrobi commented Jan 4, 2017

ssce.zip

Added some more tests that fail in some way or the other (may be related or completely new issues).
Can't seem to add multiple args1, arg2 / ret1, ret2 pairs as it dies at an eval stage.

Only the first test case works, the rest fail. Some even die and stop the others from executing which is even weirder.

@michalbundyra
Copy link
Member

@krodyrobi is it still an issue?

@krodyrobi
Copy link
Author

ssce.zip

php 7.2.1

"phpunit/phpunit": "5.7.27",
"mockery/mockery": "1.0",
"php-mock/php-mock-mockery": "1.2.0"

PHPUnit 5.7.27 by Sebastian Bergmann and contributors.

.FF..E                                                              6 / 6 (100%)

Time: 285 ms, Memory: 4.00MB

There was 1 error:

1) minify\Test_PL_Minify::test_concat_3

Mockery\Exception\BadMethodCallException: Received Mockery_1_phpmock_integration097b6adf00e967240b711ee1b842ac1c_MockDelegateFunction::delegate(), but no expectations were specified

/usr/src/myapp/vendor/php-mock/php-mock/classes/Mock.php:124
/usr/src/myapp/vendor/php-mock/php-mock/classes/generator/MockFunctionGenerator.php:108
/usr/src/myapp/src/minify.php:22
/usr/src/myapp/tests/minify.php:97


--

There were 2 failures:

1) minify\Test_PL_Minify::test_minify_page_minification
Failed asserting that two strings are identical.

--- Expected
+++ Actual
@@ @@
-mini
+random

/usr/src/myapp/tests/minify.php:53


2) minify\Test_PL_Minify::test_minimal
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-hello hello
+test_arg test_arg
 
/usr/src/myapp/tests/minify.php:64

ERRORS!

Tests: 6, Assertions: 9, Errors: 1, Failures: 2.

@michalbundyra
Copy link
Member

@krodyrobi So...

It is possible to overload only methods which are called in the namespace and these can't be defined in that namespace.

So I've analized first failing test:

    function test_minify_page_minification() {
        $true_constant = PHPMockery::mock(__NAMESPACE__, 'true_constant');
        $true_constant->once()
            ->with('PL_MINIFY_HTML')
            ->andReturn(true);

        $input = 'random';

        $minify_html = PHPMockery::mock(__NAMESPACE__, 'minify_html');
        $minify_html->once()
            ->with($input)->andReturn('mini');

        $actual = pl_minify_page($input);

        $this->assertSame('mini', $actual);
    }

so you try to mock true_constant and minify_html which are called in pl_minify_page.
It's not possible to mock these methods because these methods in that namespace are already defined and loaded.

Exactly the same in case test_minimal, function minimal is defined and already loaded in that namespace so it's not possible to overload it.

So as the real issue I can consider only error in test test_concat_3. Need to find out more about Mockery to check how it works there, and if we can resolve it.

@michalbundyra
Copy link
Member

@krodyrobi Ok, I think I have solution for mockery:
php-mock/php-mock-mockery#9

php-mock-phpunit is fine, similar issue we have in php-mock-prophecy, but there is already PR with the solution: php-mock/php-mock-prophecy#6

@krodyrobi
Copy link
Author

krodyrobi commented Mar 23, 2018

That seems to be it, is there a composer version with which I can check?
Edit: nvm I noticed composer can clone a repo

So for the first part, if I got it right, because php has caching on the defined functions I need to call the mocked function from a test namespace != than the one it was defined in, but when mocking I still need to target definitions namespace.

Will check this out later and see what pops up.

@krodyrobi
Copy link
Author

So the branch in the pr works those tests pass.

But i still haven't got a clue how to mock the other functions :D.

@michalbundyra
Copy link
Member

@krodyrobi

But i still haven't got a clue how to mock the other functions :D.

What other function? What you try to do?

As I said, you can't overload function defined in the namespace, and you can't mock function called with context without namespace.

The purpose of the library is to allow mocking built-in PHP functions via overloading them in the namespace where these are called.

@krodyrobi
Copy link
Author

I'm trying to mock a function that is not builtin, and I guess is out of scope.
Thanks for finding a fix, I'll find a way for the rest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants