Giter VIP home page Giter VIP logo

shmock's Introduction

Shmock (SHorthand for MOCKing)

What is this?

Shmock is a smooth alternative for creating mocks with PHPUnit that uses the mock/replay concept from EasyMock but uses closures to define the scope for mocking.

  <?php
  namespace Foo;

  /**
   * Here's a class we're trying to test yay.
   */
  class Foo
  {
  	private $foo = 0;
  	private $incrementing_service = null;

  	public function __construct(Incrementing_Service $incrementing_service)
  	{
  		$this->incrementing_service = $incrementing_service;
  	}

  	public function next_foo()
  	{
  		$this->foo = $this->incrementing_service->increment($this->foo);
  		return $this->foo;
  	}
  }

  /**
   * Our test case runs the same test case twice - once with the original PHPUnit mocking
   * syntax and a second time with Shmock syntax.
   */
  class Foo_Test extends PHPUnit_Framework_TestCase
  {
              use \Shmock\Shmockers; // This enables the use of the Shmock helper methods (replicated below)

              public function test_phpunit_original_mocking_syntax()
  	{
  		// this is the original PHPUnit mock syntax

  		$incrementing_service_mock = $this->getMock('\Foo\Incrementing_Service', array('increment'));
  		$incrementing_service_mock->expects($this->once())
  			->method('increment')
  			->with($this->equalTo(0))
  			->will($this->returnValue(1));

  		$foo = new Foo($incrementing_service_mock);
  		$this->assertEquals(1, $foo->next_foo(0));
  	}

  	/**
  	 * Create a shmock representation for $class_name and configure expected
  	 * mock interaction with $conf_closure
  	 * @return Shmock A fully configured mock object
  	 * @note You do not need this protected method if you use the Shmockers trait, shown above
  	 */
  	protected function shmock($class_name, $conf_closure)
  	{
  		return \Shmock\Shmock::create_class($this, $class_name, $conf_closure);
  	}

  	public function test_shmock_syntax()
  	{
  		// here's shmock. Neat huh?
  		$incrementing_service_mock = $this->shmock('\Foo\Incrementing_Service', function($shmock)
  		{
  			$shmock->increment(0)->return_value(1);
  		});

  		$foo = new Foo($incrementing_service_mock);
  		$this->assertEquals(1, $foo->next_foo(0));
  	}
  }

Installation

Shmock can be installed directly from Packagist.

"require": {
    "box/shmock": "1.0.0.x-dev"
}

Alternatively you can download Shmock.php and Shmockers.php into your test directory and run

  require_once 'Shmock.php';

PHPUnit should already be on the load path for this to work.

Type Safety

Shmock is typesafe by default and will attempt to tell you when you're using the wrong mocking approach. Shmock will throw errors in cases where:

  • You mock a static method as though it were an instance method or vice versa.
  • You mock a private method as though it were protected or public.
  • You mock a method that does not exist and there is no __call / __callStatic magic method provided.

These checks can be disabled by calling $mock_object->disable_strict_method_checking() inside the shmock closure. We also plan on supporting parameter and return value checking if it complies with yet-to-be-defined PHPDoc conventions.

Documentation:

http://box.github.io/shmock/namespaces/Shmock.html

Full list of Shmock features:

<?php
// This code could conceptually be part of a test method from the above Foo_Test class
  $inc_service = $this->shmock('\Foo\Incrementing_Service', function($my_class_shmock) // [1]
  {
  	$my_class_shmock->no_args_method(); // [2]
  	$my_class_shmock->two_arg_method('param1', 'param2'); // [3]
  	$my_class_shmock->method_that_returns_a_number()->return_value(100); // [4]
  	$my_class_shmock->method_that_gets_run_twice()->times(2); // [5]
  	$my_class_shmock->method_that_gets_run_any_times()->any(); // [6]

  	$my_class_shmock->method_puts_it_all_together('with', 'args')->times(2)->return_value(false);

  	$my_class_shmock->method_returns_another_mock()->return_shmock('\Another_Namespace\Another_Class', function($another_class) // [7]
  	{
  		$another_class->order_matters(); // [8]
  		$another_class->disable_original_constructor(); // [9a]
  		$another_class->set_constructor_arguments(1, 'Foo'); // [9b]

  		$another_class->method_dies_horribly()->throw_exception(new InvalidArgumentException()); // [10]

  		$another_class->method_gets_stubbed(1,2)->will(function(PHPUnit_Framework_MockObject_Invocation $invocation)
  		{
  			$a = $invocation->parameters[0];
  			$b = $invocation->parameters[1];
  			return $a + $b; // [11]
  		});
  	});

  	$my_class_shmock->shmock_class(function($Inc_Service)
  	{
  		$Inc_Service->my_static_method()->any()->return_value('This was returned inside the mock instance using the static:: prefix'); // [12]
  	});

  })

  $another_class = $this->shmock_class('\Another_Namespace\Another_Class', function($Another_Class) // [13]
  {
  	$Another_Class->a_static_method()->return_value(1);
  });
  1. Shmock lets you configure a mock object inside a closure. You work with a proxy object that feels like the real thing.
  2. Invoking a method sets up the expectation that it will be called once.
  3. Invoking a method with arguments causes it to expect those arguments when actually invoked.
  4. You can return values from specific invocations. In the example, the value 100 will be returned when you call the method.
  5. You can specify an expectation for the number of times a method will be called. By default it's expected once.
  6. Or you can specify "0 or more" times with any()
  7. You can nest your Shmock invocations, letting you define your mock dependencies elegantly. (If you have a two-way dependency, you can always just return_value($other_shmock) and define it somewhere else )
  8. On an object-level you can specify "order matters", meaning that the ordering of function invocations should be asserted against as well. Under the hood, this uses PHPUnit's at(N) calls automatically
  9. You have some options as far as defining constructor arguments. a) You can opt to disable the original constructor. Normally PHPUnit will run the original constructor. b) You can run the original constructor with the given arguments.
  10. Instead of returning a value, you can throw an exception when a method gets called.
  11. Even more sophisticated, you can execute an arbitrary closure when the function gets called.
  12. If you want to mock static functions, you call shmock_class which will give you all the same Shmock semantics as instances (where it makes sense). This is particularly useful when you want to partially mock an object, keeping some of the original behavior, but mocking out static / protected methods that may exist that the method you are testing is dependent on.
  13. You can also mock a class independently of a mock instance.

Copyright and License

Copyright 2014 Box, Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

shmock's People

Contributors

anthonybishopric avatar drobertduke avatar gms8994 avatar huggsboson avatar jmarrama avatar nzakas avatar swaroopb avatar theilig 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shmock's Issues

Shmock doesn't work with PHPunit 5.4 due to change in interface

PHP Fatal error: Class Shmock\InvocationImpl contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_MockObject_Invocation::generateReturnValue) in /vendor/box/shmock/src/Shmock/StaticSpec.php on line 572

sebastian/diff 1.1 version requirement prevents newer version of phpunit to be used

I am using this library in my project called TestScribe

Because shmock currently requires 1.1 version of diff component, I got the following error when trying to use a newer version of phpunit.

Output below:

Your requirements could not be resolved to an installable set of packages.

Problem 1
- Conclusion: remove sebastian/diff 1.1.0
- box/shmock 2.0.0-alpha6 requires sebastian/diff 1.1 -> satisfiable by sebastian/diff[1.1.0].
- box/shmock 2.0.0-alpha6 requires sebastian/diff 1.1 -> satisfiable by sebastian/diff[1.1.0].
- Conclusion: don't install sebastian/diff 1.1.0|keep sebastian/diff 1.1.0
- Installation request for box/shmock 2.0.0-alpha6 -> satisfiable by box/shmock[2.0.0-alpha6].

Setting dont_preserve_original_methods interferes with mocking __call methods

e.g.


class MagicMethodTest
{
    /**
     * @param string $methodName
     * @param array  $argument
     *
     * @return string
     */
    public function __call($methodName, array $argument)
    {
        return $methodName;
    }
}

class MagicMethodUsage
{
    /**
     * @param \Box\PHPUnitTestGenerator\tests\_fixture\_input\MagicMethodTest $magicTest
     *
     * @return string
     */
    public function callMagicMethod(
        MagicMethodTest $magicTest
    )
    {
        $rc = $magicTest->foo();

        return $rc;
    }
}

<?php

class MagicMethodUsageGenTest extends \PHPUnit_Framework_TestCase
{
    use \Shmock\Shmockers;

    public function testCallMagicMethod()
    {
        // Setup mocks for parameters to the method under test.

        $mockMagicMethodTest0 = $this->shmock(
            'MagicMethodTest',
            function (
                $shmock
            ) {
//                $shmock->dont_preserve_original_methods();

                /** @var $mock \Shmock\Spec */
                $mock = $shmock->__call('foo', array (
                ));
                $mock->return_value('bar');
            }
        );

        // Execute the method under test.

        $objectUnderTest = new MagicMethodUsage();
        $executionResult = $objectUnderTest->callMagicMethod($mockMagicMethodTest0);

        // Validate the execution result.

        $expected = 'bar';
        $this->assertSame(
            $expected,
            $executionResult,
            'Variable ( executionResult ) doesn\'t have the expected value.'
        );
    }
}

Use dont_preserve_original_methods and order_matters together for ClassBuilderStaticClass yields unexpected result

Here is an example that illustrates the issue:

shmock_class( 'Box_Helper_URL', function ( $shmock /** @var Box_Helper_URL|\Shmock\ClassBuilderStaticClass $shmock */ ) { // Keep track of the order of calls made on this mock. $shmock->order_matters(); // Mock all methods, return null by default unless overwritten by the expectations below. $shmock->dont_preserve_original_methods(); $shmock->disable_original_constructor(); $mock = $shmock->url_is_box('ab.com'); /** @var $mock \Shmock\Spec */ $mock->return_value(true); } ); \Statics::overwrite('Box_Helper_URL', $mockBox_Helper_URL0); // Execute the method under test. $objectUnderTest = new \StaticsUsageExample(); $executionResult = $objectUnderTest->isBoxUrl('ab.com'); // Validate the execution result. $expected = true; $this->assertSame( $expected, $executionResult, 'Variable ( executionResult ) doesn\'t have the expected value.' ); ``` } } The test failed. The url_is_box call on the mock object returned null instead of true as I expected. # Here is my analysis of the issue: This is what I found that caused the failure: dont_preserve_original_methods calls $this->__call($method->getName(), [])->any()->return_null(); to overwrite the static method calls of the class being mocked. This has the same effect as specifying $shmock->url_is_box()->any()->return_value(null); in the mock object expectations. Since order_matter is set, in effect the expectation becomes that url_is_box will be called once first and return null and it should be called again and this time return true. This effect is not what I expected. And there is no documentation that I can find that describes this behavior. The non static class builder has a different implementation and doesn't seem to exhibit this behavior.

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.