Giter Site home page Giter Site logo

phockito's Introduction

Phockito - Mockito for PHP

Mocking framework inspired by Mockito for Java

Checkout the original's website for the philosophy behind the API and more examples (although be aware that this is only a partial implementation for now)

Thanks to the developers of Mockito for the inspiration, and hamcrest-php for making this easy.

Example mocking:

// Create the mock
$iterator = Phockito::mock('ArrayIterator');

// Use the mock object - doesn't do anything, functions return null
$iterator->append('Test');
$iterator->asort();

// Selectively verify execution
Phockito::verify($iterator)->append('Test');
// 1 is default - can also do 2, 3  for exact numbers, or 1+ for at least one, or 0 for never
Phockito::verify($iterator, 1)->asort();

If PHPUnit is available, on failure verify throws a PHPUnit_Framework_AssertionFailedError (looks like an assertion failure), otherwise just throws an Exception

Example stubbing:

// Create the mock
$iterator = Phockito::mock('ArrayIterator');

// Stub in a value
Phockito::when($iterator->offsetGet(0))->return('first');

// Prints "first"
print_r($iterator->offsetGet(0));

// Prints null, because get(999) not stubbed
print_r($iterator->offsetGet(999));

Alternative API, jsMockito style

// Stub in a value
Phockito::when($iterator)->offsetGet(0)->return('first');

Spies

Mocks are full mocks - method calls to unstubbed function always return null, and never call the parent function.

You can also create partial mocks by calling spy instead of mock. With spies, method calls to unstubbed functions call the parent function.

Because spies are proper subclasses, this lets you stub in methods that are called by other methods in a class

class A {
	function Foo(){ return 'Foo'; }
	function Bar(){ return $this->Foo() . 'Bar'; }
}

// Create a mock
$mock = Phockito::mock('A');
print_r($mock->Foo()); // 'null'
print_r($mock->Bar()); // 'null'

// Create a spy
$spy = Phockito::spy('A');
print_r($spy->Foo()); // 'Foo'
print_r($spy->Bar()); // 'FooBar'

// Stub a method 
Phockito::when($spy)->Foo()->return('Zap');
print_r($spy->Foo()); // 'Zap'
print_r($spy->Bar()); // 'ZapBar'

Argument matching

Phockito allows the use of Hamcrest matchers on any argument. Hamcrest is a library of "matching functions" that, given a value, return true if that value matches some rule.

Hamcrest matchers are not included by default, so the first step is to call Phockito::include_hamcrest(); immediately after including Phockito. Note that this will import the Hamcrest matchers as global functions - passing false as an argument will keep your namespace clean by making all matchers only available as static methods of Hamcrest (at the expense of worse looking test code).

Once included you can pass a Hamcrest matcher as an argument in your when or verify rule, eg:

class A {
	function Foo($a){ }
}

$stub = Phockito::mock('A');
Phockito::when($stub)->Foo(anything())->return('Zap');

Some common Hamcrest matchers:

  • Core
    • anything - always matches, useful if you don't care what the object under test is
  • Logical
    • allOf - matches if all matchers match, short circuits (like PHP &&)
    • anyOf - matches if any matchers match, short circuits (like PHP ||)
    • not - matches if the wrapped matcher doesn't match and vice versa
  • Object
    • equalTo - test object equality using the == operator
    • anInstanceOf - test type
    • notNullValue, nullValue - test for null
  • Number
    • closeTo - test floating point values are close to a given value
    • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - test ordering
  • Text
    • equalToIgnoringCase - test string equality ignoring case
    • equalToIgnoringWhiteSpace - test string equality ignoring differences in runs of whitespace
    • containsString, endsWith, startsWith - test string matching

Differences from Mockito

Stubbing methods more flexible

In Mockito, the methods when building a stub are limited to thenReturns, thenThrows. In Phockito, you can use any method as long as it has 'return' or 'throw' in it, so Phockito::when(...)->return(1)->thenReturn(2) is fine.

Type-safe argument matching

In Mockito, to use a Hamcrest matcher, the argThat method is used to satisfy the type checker. In PHP, a little extra help is needed. Phockito provides the argOfTypeThat for provided Hamcrest matchers to type-hinted parameters:

class A {
    function Foo(B $b){ }
}

class B {}

$stub = Phockito::mock('A');
$b = new B();
Phockito::when($stub)->Foo(argOfTypeThat('B', is(equalTo($b))))->return('Zap');

It's also possible to pass a mock to 'when', rather than the result of a method call on a mock, e.g. Phockito::when($mock)->methodToStub(...)->thenReturn(...). This side-steps the type system entirely.

Note that argOfTypeThat is only compatible with object type-hints; arguments with array or callable type-hints cannot be handled in a type-safe way.

Verify 'times' argument changed

In Mockito, the 'times' argument to verify is an object of interface VerificationMode (like returned by the functions times, atLeastOnce, etc).

For now we just take either an integer, or an integer followed by '+'. It's not extensible.

Callback instead of answers

In Mockito, you can return dynamic results from a stubbed method by calling thenAnswer with an instance of an object that has a specific method. In Phockito you call thenCallback with a callback argument, which gets called with the arguments the stubbed method was called with.

Default arguments

PHP has default arguments, unlike Java. If you don't specify a default argument in your stub or verify matcher, it'll match the default argument.

class Foo {
  function Bar($a, $b = 2){ /* NOP */ }
}

// Create the mock
$mock = Phockito::mock('Foo');

// Set up a stub
Phockito::when($mock->Bar(1))->return('A');

$mock->Bar(1); // Returns 'A'
$mock->Bar(1, 2); // Also returns 'A'
$mock->Bar(1, 3); // Returns null, since no stubbed return value matches

Return typing

Mockito returns a type-compatible false, based on the declared return type. We don't have defined type values in PHP, so we always return null. TODO: Support using phpdoc @return when declared.

TODO

  • Mockito-specific hamcrest matchers (anyString, etc)
  • Ordered verification

License

Copyright (C) 2012 Hamish Friedlander / SilverStripe.

Distributable under either the same license as SilverStripe or the Apache Public License V2 (http://www.apache.org/licenses/LICENSE-2.0.html) at your choice

You don’t have to do anything special to choose one license or the other and you don’t have to notify anyone which license you are using.

Hamcrest-php is under it's own license - see hamcrest-php/LICENSE.txt.

phockito's People

Contributors

chillu avatar drdamour avatar fredrikwendt avatar rowanhill 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

phockito's Issues

AssertThat() already exists in later versions of PHPUnit

While running some tests locally I stumbled into this error

 Fatal error: Cannot redeclare assertThat() (previously declared in /..../vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions.php:1637) in /...../vendor/hafriedlander/phockito/hamcrest-php/hamcrest/Hamcrest.php on line 32

It seems like the latest version of phpunit has the same function in the global scope

https://github.com/sebastianbergmann/phpunit/blob/master/src/Framework/Assert/Functions.php#L1671
as
https://github.com/hafriedlander/phockito/blob/master/hamcrest-php/hamcrest/Hamcrest.php#L25

SS manifest overloading won't work with 3.1

PhockitoSilverStripe and PhockitoSilverStripeTest. I'm not even sure what it does, but I'm pretty sure whatever it is won't work in 3.1 due to the changed way of persisting the manifest ;)

Type hints & Hamcrest matchers

Hi,

When stubbing a method, Hamcrest matchers cannot be used for parameters which have type hints. Attempting to do so raises an exception along the lines of:
PHPUnit_Framework_Error : Argument 1 passed to __phockito_PhockitoHamcrestTest_MockMe_Mock::Baz() must be an instance of PhockitoHamcrestTest_PassMe, instance of Hamcrest_Core_IsInstanceOf given

Is there a recommended work-around for this?

Thanks,
Rowan

Stubbing override

Only the first stubbing works.

when($mock->foo())->return(1);
when($mock->foo())->return(2);
when($mock->foo())->return(3);
$mock->foo(); // returns 1

It should return 3.

Spies seem to have reserved words for arguments.

This is a generated method for a spy:

public function myMethod( $request, $response ){
    $args = func_get_args();

    $backtrace = debug_backtrace();
    $instance = $backtrace[0]['type'] == '::' ? ('::'.'tag_test_mocks_routing_controller') : $this->__phockito_instanceid;

    // The value for $response that was passed to the method is
    // now replaced by this value for $response.
    $response = \Phockito::__called('tag_test_mocks_routing_controller', $instance, 'myMethod', $args);

    if ($response) return \Phockito::__perform_response($response, $args);
    else return parent::myMethod( $request, $response );
  }

Because $backtrace, $instance, $response, and $args are all assigned in this method, if used as function parameters, then the value passed to the method is invisibly replaced by the value in this function.

This means for full functionality with phockito, functions should not take any of those as arguments. The 'bug' manifests itself as function parameters mysteriously becoming null when the function is called on a spy, even though the value passed to the function is non-null. This occurs if no stub/responder has been set for the spy.

Normally I'd submit a pull request with a fix but I'm not actually sure the best way to fix this, since obfuscating the variable names has the same problem, plus makes the code unreadable.

And using call_user_func() seemed to have issues resolving parent when I tried.

Pronunciation

Is it supposed to be:

fockito
fuhckito (like vietnamise pho)
pohckito
something else?

Nice project by the way - certainly helps reduce context switching when moving between PHP/Java.

Have mocked methods throw "notmocked" exceptions instead of returning null optionally

I see a lot of test fail silently because the methods on a mock return null automatically. This can be attributed in a weakness to code, but it's difficult to discover such a weakness in the implementation.

What i propose is adding a second optional parameter to Phockito::mock that indicates that instead of returning null for all methods in a mock, instead some exception be thrown. this would result in many tests failing loudly instead of failing silently.

thoughts?

Throw exception which takes constructor argument impossible

Hello there.

Suppose I want to mock some class, and that class needs to throw an exception which takes a constructor argument. This seems impossible with the current api.

It does also seem like Phockito is different from Mockito on this point. Mockito syntax would be:
Phockito->when($foo)->bar()->throw(new BazException("Argument here");

The only change needed is to replace
else if ($response['action'] == 'throw') { $class = $response['value']; throw new $class(); }
with
else if ($response['action'] == 'throw') { $class = $response['value']; throw $response['value']; }

What do you think? This would break old tests, and therefore should probably use some other method to register the throwable class. Maybe ->throwInstance(new BazException("foo"))

Can't mock SoapClient

This (currently) fails because of the reflection trying to get default values for arguments which - if I understand it correctly - comes from the fact that SoapClass is a native class (and the Reflection* doesn't really work with that?).

using namespaces

Hi, I try to create a mock object using namespace

$this->screen = Phockito::mock('\com\holatdd\Screen');

but have a error message

PHP Parse error: syntax error, unexpected T_NS_SEPARATOR, expecting '{' in /usr/share/php/phockito/Phockito.php(256) : eval()'d code on line 1
PHP Fatal error: Class '_phockito\com\holatdd\Screen_Mock' not found in /usr/share/php/phockito/Phockito.php on line 287

Thanks!

Sorry my poor english

Isidro

Phpstorm cant find Phockito_Mock

in the docs for verify the type for the first argument is Phockito_Mock:

    /**
     * Verify builder. Takes a mock instance and an optional number of times to verify against. Returns a
     * DSL object that catches the method to verify
     *
     * @static
     * @param Phockito_Mock $mock - The mock instance to verify
     * @param string $times - The number of times the method should be called, either a number, or a number followed by "+"
     * @return Phockito_VerifyBuilder
     */

This type/class cant be found.

Type Hinting

About type hinting in PHP: http://www.php.net/manual/en/language.oop5.typehinting.php

Classes with type-hinted methods cannot be mocked.

Code:

<?
Phockito::mock("Foo");

class Foo {

    public function doStuff(Bar $bar) {
    }
}

class Bar {
}

results in:
Fatal error: Declaration of __phockito_Foo_Mock must be compatible with that of Foo in (...)

Stubbing override, continued

I'm not happy with the response in issue #5 as the behaviour in Phockito differs from Mockito.
The following test passes in Mockito

package org.richardsson;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class OverwriteTest {
    public static class Foo {
        public int bar() { return -1; }
    }

    @Test
    public void shouldOverride() {
        Foo fooMock = mock(Foo.class);

        when(fooMock.bar()).thenReturn(0);
        when(fooMock.bar()).thenReturn(1);
        when(fooMock.bar()).thenReturn(2);

        assertEquals(2, fooMock.bar());
        assertEquals(2, fooMock.bar());
        assertEquals(2, fooMock.bar());
    }
}

that is, the latest return value is used. To me i makes a lot of sense and is something I often use when writing java code. I usually set up the mocks for the main flow in the the setUp() method which means that test methods that verify that flow do not need any mock code. The test methods that verify other code paths on the other hand, clearly indicate the difference in mock behaviour by overrides. With the current Phockito implementation this is only possible by using reset() which adds noise.

It also turns out that the current Phockito implementation does not behave as described in issue #5, so I suppose it is broken.

when($mock->foo())->return(1);
when($mock->foo())->return(2);
when($mock->foo())->return(3);

// Expected behaviour described in issue #5
$mock->foo(); // returns 1
$mock->foo(); // returns 2
$mock->foo(); // returns 3

// Actual behaviour
$mock->foo(); // returns 1
$mock->foo(); // returns 1
$mock->foo(); // returns 1

// Mockito way
$mock->foo(); // returns 3
$mock->foo(); // returns 3
$mock->foo(); // returns 3

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.