Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -1,12 +0,0 @@
*~
pearfarm.spec
*.sublime-project
library/Hamcrest/*
composer.lock
vendor/
composer.phar
test.php
build/
phpunit.xml
*.DS_store
.idea/*

View File

@@ -1,14 +0,0 @@
<?php
$finder = Symfony\CS\Finder\DefaultFinder::create()
->exclude('examples')
->exclude('docs')
->exclude('travis')
->exclude('vendor')
->exclude('tests/Mockery/_files')
->exclude('tests/Mockery/_files')
->in(__DIR__);
return Symfony\CS\Config\Config::create()
->level('psr2')
->finder($finder);

View File

@@ -0,0 +1,11 @@
<?php
namespace PHPSTORM_META;
override(\Mockery::mock(0), map(["" => "@"]));
override(\Mockery::spy(0), map(["" => "@"]));
override(\Mockery::namedMock(0), map(["" => "@"]));
override(\Mockery::instanceMock(0), map(["" => "@"]));
override(\mock(0), map(["" => "@"]));
override(\spy(0), map(["" => "@"]));
override(\namedMock(0), map(["" => "@"]));

View File

@@ -1,24 +0,0 @@
filter:
paths: [library/*]
excluded_paths: [vendor/*, tests/*, examples/*]
before_commands:
- 'composer install --dev --prefer-source'
tools:
external_code_coverage:
timeout: 300
php_code_sniffer: true
php_cpd:
enabled: true
excluded_dirs: [vendor, tests, examples]
php_pdepend:
enabled: true
excluded_dirs: [vendor, tests, examples]
php_loc:
enabled: true
excluded_dirs: [vendor, tests, examples]
php_hhvm: false
php_mess_detector: true
php_analyzer: true
changetracking:
bug_patterns: ["\bfix(?:es|ed)?\b"]
feature_patterns: ["\badd(?:s|ed)?\b", "\bimplement(?:s|ed)?\b"]

View File

@@ -1 +0,0 @@
preset: psr2

View File

@@ -1,42 +0,0 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
- hhvm-nightly
matrix:
allow_failures:
- php: hhvm
- php: hhvm-nightly
sudo: false
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- travis_retry ./travis/install.sh
before_script:
- ./travis/before_script.sh
script:
- ./travis/script.sh
after_success:
- ./travis/after_success.sh
notifications:
email:
- padraic.brady@gmail.com
- dave@atstsolutions.co.uk
irc: "irc.freenode.org#mockery"

View File

@@ -1,5 +1,178 @@
# Change Log
## 1.3.6 (2022-09-07)
* PHP 8.2 | Fix "Use of "parent" in callables is deprecated" notice #1169
## 1.5.1 (2022-09-07)
* [PHP 8.2] Various tests: explicitly declare properties #1170
* [PHP 8.2] Fix "Use of "parent" in callables is deprecated" notice #1169
* [PHP 8.1] Support intersection types #1164
* Handle final `__toString` methods #1162
## 1.5.0 (2022-01-20)
* Override default call count expectations via expects() #1146
* Mock methods with static return types #1157
* Mock methods with mixed return type #1156
* Mock classes with new in initializers on PHP 8.1 #1160
* Removes redundant PHPUnitConstraint #1158
## 1.4.4 (2021-09-13)
* Fixes auto-generated return values #1144
* Adds support for tentative types #1130
* Fixes for PHP 8.1 Support (#1130 and #1140)
* Add method that allows defining a set of arguments the mock should yield #1133
* Added option to configure default matchers for objects `\Mockery::getConfiguration()->setDefaultMatcher($class, $matcherClass)` #1120
## 1.3.5 (2021-09-13)
* Fix auto-generated return values with union types #1143
* Adds support for tentative types #1130
* Fixes for PHP 8.1 Support (#1130 and #1140)
* Add method that allows defining a set of arguments the mock should yield #1133
* Added option to configure default matchers for objects `\Mockery::getConfiguration()->setDefaultMatcher($class, $matcherClass)` #1120
## 1.4.3 (2021-02-24)
* Fixes calls to fetchMock before initialisation #1113
* Allow shouldIgnoreMissing() to behave in a recursive fashion #1097
* Custom object formatters #766 (Needs Docs)
* Fix crash on a union type including null #1106
## 1.3.4 (2021-02-24)
* Fixes calls to fetchMock before initialisation #1113
* Fix crash on a union type including null #1106
## 1.4.2 (2020-08-11)
* Fix array to string conversion in ConstantsPass (#1086)
* Fixed nullable PHP 8.0 union types (#1088, #1089)
* Fixed support for PHP 8.0 parent type (#1088, #1089)
* Fixed PHP 8.0 mixed type support (#1088, #1089)
* Fixed PHP 8.0 union return types (#1088, #1089)
## 1.4.1 (2020-07-09)
* Allow quick definitions to use 'at least once' expectation
`\Mockery::getConfiguration()->getQuickDefinitions()->shouldBeCalledAtLeastOnce(true)` (#1056)
* Added provisional support for PHP 8.0 (#1068, #1072,#1079)
* Fix mocking methods with iterable return type without specifying a return value (#1075)
## 1.3.3 (2020-08-11)
* Fix array to string conversion in ConstantsPass (#1086)
* Fixed nullable PHP 8.0 union types (#1088)
* Fixed support for PHP 8.0 parent type (#1088)
* Fixed PHP 8.0 mixed type support (#1088)
* Fixed PHP 8.0 union return types (#1088)
## 1.3.2 (2020-07-09)
* Fix mocking with anonymous classes (#1039)
* Fix andAnyOthers() to properly match earlier expectations (#1051)
* Added provisional support for PHP 8.0 (#1068, #1072,#1079)
* Fix mocking methods with iterable return type without specifying a return value (#1075)
## 1.4.0 (2020-05-19)
* Fix mocking with anonymous classes (#1039)
* Fix andAnyOthers() to properly match earlier expectations (#1051)
* Drops support for PHP < 7.3 and PHPUnit < 8 (#1059)
## 1.3.1 (2019-12-26)
* Revert improved exception debugging due to BC breaks (#1032)
## 1.3.0 (2019-11-24)
* Added capture `Mockery::capture` convenience matcher (#1020)
* Added `andReturnArg` to echo back an argument passed to a an expectation (#992)
* Improved exception debugging (#1000)
* Fixed `andSet` to not reuse properties between mock objects (#1012)
## 1.2.4 (2019-09-30)
* Fix a bug introduced with previous release, for empty method definition lists (#1009)
## 1.2.3 (2019-08-07)
* Allow mocking classes that have allows and expects methods (#868)
* Allow passing thru __call method in all mock types (experimental) (#969)
* Add support for `!` to blacklist methods (#959)
* Added `withSomeOfArgs` to partial match a list of args (#967)
* Fix chained demeter calls with type hint (#956)
## 1.2.2 (2019-02-13)
* Fix a BC breaking change for PHP 5.6/PHPUnit 5.7.27 (#947)
## 1.2.1 (2019-02-07)
* Support for PHPUnit 8 (#942)
* Allow mocking static methods called on instance (#938)
## 1.2.0 (2018-10-02)
* Starts counting default expectations towards count (#910)
* Adds workaround for some HHVM return types (#909)
* Adds PhpStorm metadata support for autocomplete etc (#904)
* Further attempts to support multiple PHPUnit versions (#903)
* Allows setting constructor expectations on instance mocks (#900)
* Adds workaround for HHVM memoization decorator (#893)
* Adds experimental support for callable spys (#712)
## 1.1.0 (2018-05-08)
* Allows use of string method names in allows and expects (#794)
* Finalises allows and expects syntax in API (#799)
* Search for handlers in a case instensitive way (#801)
* Deprecate allowMockingMethodsUnnecessarily (#808)
* Fix risky tests (#769)
* Fix namespace in TestListener (#812)
* Fixed conflicting mock names (#813)
* Clean elses (#819)
* Updated protected method mocking exception message (#826)
* Map of constants to mock (#829)
* Simplify foreach with `in_array` function (#830)
* Typehinted return value on Expectation#verify. (#832)
* Fix shouldNotHaveReceived with HigherOrderMessage (#842)
* Deprecates shouldDeferMissing (#839)
* Adds support for return type hints in Demeter chains (#848)
* Adds shouldNotReceive to composite expectation (#847)
* Fix internal error when using --static-backup (#845)
* Adds `andAnyOtherArgs` as an optional argument matcher (#860)
* Fixes namespace qualifying with namespaced named mocks (#872)
* Added possibility to add Constructor-Expections on hard dependencies, read: Mockery::mock('overload:...') (#781)
## 1.0.0 (2017-09-06)
* Destructors (`__destruct`) are stubbed out where it makes sense
* Allow passing a closure argument to `withArgs()` to validate multiple arguments at once.
* `Mockery\Adapter\Phpunit\TestListener` has been rewritten because it
incorrectly marked some tests as risky. It will no longer verify mock
expectations but instead check that tests do that themselves. PHPUnit 6 is
required if you want to use this fail safe.
* Removes SPL Class Loader
* Removed object recorder feature
* Bumped minimum PHP version to 5.6
* `andThrow` will now throw anything `\Throwable`
* Adds `allows` and `expects` syntax
* Adds optional global helpers for `mock`, `namedMock` and `spy`
* Adds ability to create objects using traits
* `Mockery\Matcher\MustBe` was deprecated
* Marked `Mockery\MockInterface` as internal
* Subset matcher matches recursively
* BC BREAK - Spies return `null` by default from ignored (non-mocked) methods with nullable return type
* Removed extracting getter methods of object instances
* BC BREAK - Remove implicit regex matching when trying to match string arguments, introduce `\Mockery::pattern()` when regex matching is needed
* Fix Mockery not getting closed in cases of failing test cases
* Fix Mockery not setting properties on overloaded instance mocks
* BC BREAK - Fix Mockery not trying default expectations if there is any concrete expectation
* BC BREAK - Mockery's PHPUnit integration will mark a test as risky if it
thinks one it's exceptions has been swallowed in PHPUnit > 5.7.6. Use `$e->dismiss()` to dismiss.
## 0.9.4 (XXXX-XX-XX)
* `shouldIgnoreMissing` will respect global `allowMockingNonExistentMethods`
@@ -19,7 +192,7 @@
## 0.9.2 (2014-09-03)
* Some workarounds for the serilisation problems created by changes to PHP in 5.5.13, 5.4.29,
* Some workarounds for the serialisation problems created by changes to PHP in 5.5.13, 5.4.29,
5.6.
* Demeter chains attempt to reuse doubles as they see fit, so for foo->bar and
foo->baz, we'll attempt to use the same foo

View File

@@ -39,7 +39,6 @@ but we'll probably merge any code that looks close enough.
* Optionally, but preferably, write some documentation
* Optionally, update the CHANGELOG.md file with your feature or
[BC](http://en.wikipedia.org/wiki/Backward_compatibility) break
* If you have created new library files, add them to the root package.xml file for PEAR install support.
* Send a [Pull
Request](https://help.github.com/articles/creating-a-pull-request) to the
correct target branch (see below)
@@ -66,7 +65,7 @@ e.g. 0.8.
To run the unit tests for Mockery, clone the git repository, download Composer using
the instructions at [http://getcomposer.org/download/](http://getcomposer.org/download/),
then install the dependencies with `php /path/to/composer.phar install --dev`.
then install the dependencies with `php /path/to/composer.phar install`.
This will install the required PHPUnit and Hamcrest dev dependencies and create the
autoload files required by the unit tests. You may run the `vendor/bin/phpunit` command
@@ -75,7 +74,7 @@ to run the unit tests. If everything goes to plan, there will be no failed tests
## Debugging Mockery
Mockery and it's code generation can be difficult to debug. A good start is to
Mockery and its code generation can be difficult to debug. A good start is to
use the `RequireLoader`, which will dump the code generated by mockery to a file
before requiring it, rather than using eval. This will help with stack traces,
and you will be able to open the mock class in your editor.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2010-2014, Pádraic Brady
Copyright (c) 2010, Pádraic Brady
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

View File

@@ -1,10 +1,9 @@
Mockery
=======
[![Build Status](https://travis-ci.org/padraic/mockery.png?branch=master)](http://travis-ci.org/padraic/mockery)
[![Latest Stable Version](https://poser.pugx.org/mockery/mockery/v/stable.png)](https://packagist.org/packages/mockery/mockery)
[![Total Downloads](https://poser.pugx.org/mockery/mockery/downloads.png)](https://packagist.org/packages/mockery/mockery)
[![Build Status](https://github.com/mockery/mockery/actions/workflows/tests.yml/badge.svg)](https://github.com/mockery/mockery/actions)
[![Latest Stable Version](https://poser.pugx.org/mockery/mockery/v/stable.svg)](https://packagist.org/packages/mockery/mockery)
[![Total Downloads](https://poser.pugx.org/mockery/mockery/downloads.svg)](https://packagist.org/packages/mockery/mockery)
Mockery is a simple yet flexible PHP mock object framework for use in unit testing
with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a
@@ -16,53 +15,277 @@ phpunit-mock-objects without the World ending.
Mockery is released under a New BSD License.
The current released version on Packagist is 0.9.3.
The current released version for PEAR is 0.9.0. Composer users may instead opt to use
the current master branch aliased to 0.9.x-dev.
## Installation
To install Mockery, run the command below and you will get the latest
version
```sh
composer require mockery/mockery
composer require --dev mockery/mockery
```
If you want to run the tests:
## Documentation
```sh
vendor/bin/phpunit
```
In older versions, this README file was the documentation for Mockery. Over time
we have improved this, and have created an extensive documentation for you. Please
use this README file as a starting point for Mockery, but do read the documentation
to learn how to use Mockery.
####Note
The current version can be seen at [docs.mockery.io](http://docs.mockery.io).
The future Mockery 0.9.4 release will be the final version to have PHP 5.3
as a minimum requirement. The minimum PHP requirement will thereafter move to
PHP 5.4. Also, the PEAR channel will go offline permanently no earlier than 30
June 2015.
## PHPUnit Integration
## Mock Objects
Mockery ships with some helpers if you are using PHPUnit. You can extend the
[`Mockery\Adapter\Phpunit\MockeryTestCase`](library/Mockery/Adapter/Phpunit/MockeryTestCase.php)
class instead of `PHPUnit\Framework\TestCase`, or if you are already using a
custom base class for your tests, take a look at the traits available in the
[`Mockery\Adapter\Phpunit`](library/Mockery/Adapter/Phpunit) namespace.
In unit tests, mock objects simulate the behaviour of real objects. They are
## Test Doubles
Test doubles (often called mocks) simulate the behaviour of real objects. They are
commonly utilised to offer test isolation, to stand in for objects which do not
yet exist, or to allow for the exploratory design of class APIs without
requiring actual implementation up front.
The benefits of a mock object framework are to allow for the flexible generation
of such mock objects (and stubs). They allow the setting of expected method calls
and return values using a flexible API which is capable of capturing every
The benefits of a test double framework are to allow for the flexible generation
and configuration of test doubles. They allow the setting of expected method calls
and/or return values using a flexible API which is capable of capturing every
possible real object behaviour in way that is stated as close as possible to a
natural language description.
natural language description. Use the `Mockery::mock` method to create a test
double.
``` php
$double = Mockery::mock();
```
## Prerequisites
If you need Mockery to create a test double to satisfy a particular type hint,
you can pass the type to the `mock` method.
Mockery requires PHP 5.3.2 or greater. In addition, it is recommended to install
the Hamcrest library (see below for instructions) which contains additional
matchers used when defining expected method arguments.
``` php
class Book {}
interface BookRepository {
function find($id): Book;
function findAll(): array;
function add(Book $book): void;
}
## Documentation
$double = Mockery::mock(BookRepository::class);
```
The current version can be seen at [docs.mockery.io](http://docs.mockery.io).
A detailed explanation of creating and working with test doubles is given in the
documentation, [Creating test doubles](http://docs.mockery.io/en/latest/reference/creating_test_doubles.html)
section.
## Method Stubs 🎫
A method stub is a mechanism for having your test double return canned responses
to certain method calls. With stubs, you don't care how many times, if at all,
the method is called. Stubs are used to provide indirect input to the system
under test.
``` php
$double->allows()->find(123)->andReturns(new Book());
$book = $double->find(123);
```
If you have used Mockery before, you might see something new in the example
above &mdash; we created a method stub using `allows`, instead of the "old"
`shouldReceive` syntax. This is a new feature of Mockery v1, but fear not,
the trusty ol' `shouldReceive` is still here.
For new users of Mockery, the above example can also be written as:
``` php
$double->shouldReceive('find')->with(123)->andReturn(new Book());
$book = $double->find(123);
```
If your stub doesn't require specific arguments, you can also use this shortcut
for setting up multiple calls at once:
``` php
$double->allows([
"findAll" => [new Book(), new Book()],
]);
```
or
``` php
$double->shouldReceive('findAll')
->andReturn([new Book(), new Book()]);
```
You can also use this shortcut, which creates a double and sets up some stubs in
one call:
``` php
$double = Mockery::mock(BookRepository::class, [
"findAll" => [new Book(), new Book()],
]);
```
## Method Call Expectations 📲
A Method call expectation is a mechanism to allow you to verify that a
particular method has been called. You can specify the parameters and you can
also specify how many times you expect it to be called. Method call expectations
are used to verify indirect output of the system under test.
``` php
$book = new Book();
$double = Mockery::mock(BookRepository::class);
$double->expects()->add($book);
```
During the test, Mockery accept calls to the `add` method as prescribed.
After you have finished exercising the system under test, you need to
tell Mockery to check that the method was called as expected, using the
`Mockery::close` method. One way to do that is to add it to your `tearDown`
method in PHPUnit.
``` php
public function tearDown()
{
Mockery::close();
}
```
The `expects()` method automatically sets up an expectation that the method call
(and matching parameters) is called **once and once only**. You can choose to change
this if you are expecting more calls.
``` php
$double->expects()->add($book)->twice();
```
If you have used Mockery before, you might see something new in the example
above &mdash; we created a method expectation using `expects`, instead of the "old"
`shouldReceive` syntax. This is a new feature of Mockery v1, but same as with
`accepts` in the previous section, it can be written in the "old" style.
For new users of Mockery, the above example can also be written as:
``` php
$double->shouldReceive('find')
->with(123)
->once()
->andReturn(new Book());
$book = $double->find(123);
```
A detailed explanation of declaring expectations on method calls, please
read the documentation, the [Expectation declarations](http://docs.mockery.io/en/latest/reference/expectations.html)
section. After that, you can also learn about the new `allows` and `expects` methods
in the [Alternative shouldReceive syntax](http://docs.mockery.io/en/latest/reference/alternative_should_receive_syntax.html)
section.
It is worth mentioning that one way of setting up expectations is no better or worse
than the other. Under the hood, `allows` and `expects` are doing the same thing as
`shouldReceive`, at times in "less words", and as such it comes to a personal preference
of the programmer which way to use.
## Test Spies 🕵️
By default, all test doubles created with the `Mockery::mock` method will only
accept calls that they have been configured to `allow` or `expect` (or in other words,
calls that they `shouldReceive`). Sometimes we don't necessarily care about all of the
calls that are going to be made to an object. To facilitate this, we can tell Mockery
to ignore any calls it has not been told to expect or allow. To do so, we can tell a
test double `shouldIgnoreMissing`, or we can create the double using the `Mocker::spy`
shortcut.
``` php
// $double = Mockery::mock()->shouldIgnoreMissing();
$double = Mockery::spy();
$double->foo(); // null
$double->bar(); // null
```
Further to this, sometimes we want to have the object accept any call during the test execution
and then verify the calls afterwards. For these purposes, we need our test
double to act as a Spy. All mockery test doubles record the calls that are made
to them for verification afterwards by default:
``` php
$double->baz(123);
$double->shouldHaveReceived()->baz(123); // null
$double->shouldHaveReceived()->baz(12345); // Uncaught Exception Mockery\Exception\InvalidCountException...
```
Please refer to the [Spies](http://docs.mockery.io/en/latest/reference/spies.html) section
of the documentation to learn more about the spies.
## Utilities 🔌
### Global Helpers
Mockery ships with a handful of global helper methods, you just need to ask
Mockery to declare them.
``` php
Mockery::globalHelpers();
$mock = mock(Some::class);
$spy = spy(Some::class);
$spy->shouldHaveReceived()
->foo(anyArgs());
```
All of the global helpers are wrapped in a `!function_exists` call to avoid
conflicts. So if you already have a global function called `spy`, Mockery will
silently skip the declaring its own `spy` function.
### Testing Traits
As Mockery ships with code generation capabilities, it was trivial to add
functionality allowing users to create objects on the fly that use particular
traits. Any abstract methods defined by the trait will be created and can have
expectations or stubs configured like normal Test Doubles.
``` php
trait Foo {
function foo() {
return $this->doFoo();
}
abstract function doFoo();
}
$double = Mockery::mock(Foo::class);
$double->allows()->doFoo()->andReturns(123);
$double->foo(); // int(123)
```
## Versioning
The Mockery team attempts to adhere to [Semantic Versioning](http://semver.org),
however, some of Mockery's internals are considered private and will be open to
change at any time. Just because a class isn't final, or a method isn't marked
private, does not mean it constitutes part of the API we guarantee under the
versioning scheme.
### Alternative Runtimes
Mockery 1.3 was the last version to support HHVM 3 and PHP 5. There is no support for HHVM 4+.
## A new home for Mockery
⚠️️ Update your remotes! Mockery has transferred to a new location. While it was once
at `padraic/mockery`, it is now at `mockery/mockery`. While your
existing repositories will redirect transparently for any operations, take some
time to transition to the new URL.
```sh
$ git remote set-url upstream https://github.com/mockery/mockery.git
```
Replace `upstream` with the name of the remote you use locally; `upstream` is commonly
used but you may be using something else. Run `git remote -v` to see what you're actually
using.

View File

@@ -1,8 +1,22 @@
{
"name": "mockery/mockery",
"description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
"keywords": ["library", "testing", "test", "stub", "mock", "mockery", "test double", "tdd", "bdd", "mock objects"],
"homepage": "http://github.com/padraic/mockery",
"description": "Mockery is a simple yet flexible PHP mock object framework",
"scripts": {
"docs": "phpdoc -d library -t docs/api"
},
"keywords": [
"bdd",
"library",
"mock",
"mock objects",
"mockery",
"stub",
"tdd",
"test",
"test double",
"testing"
],
"homepage": "https://github.com/mockery/mockery",
"license": "BSD-3-Clause",
"authors": [
{
@@ -17,19 +31,32 @@
}
],
"require": {
"php": ">=5.3.2",
"php": "^7.3 || ^8.0",
"lib-pcre": ">=7.0",
"hamcrest/hamcrest-php": "~1.1"
"hamcrest/hamcrest-php": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "^8.5 || ^9.3"
},
"conflict": {
"phpunit/phpunit": "<8.0"
},
"autoload": {
"psr-0": { "Mockery": "library/" }
"psr-0": {
"Mockery": "library/"
}
},
"autoload-dev": {
"psr-4": {
"test\\": "tests/"
}
},
"config": {
"preferred-install": "dist"
},
"extra": {
"branch-alias": {
"dev-master": "0.9.x-dev"
"dev-master": "1.4.x-dev"
}
}
}

View File

@@ -1 +0,0 @@
_build

View File

@@ -1,177 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/MockeryDocs.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/MockeryDocs.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/MockeryDocs"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/MockeryDocs"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View File

@@ -46,16 +46,16 @@ master_doc = 'index'
# General information about the project.
project = u'Mockery Docs'
copyright = u'2014, Pádraic Brady, Dave Marshall, Wouter, Graham Campbell'
copyright = u'Pádraic Brady, Dave Marshall and contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.9'
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '0.9'
release = '1.0-alpha'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -187,7 +187,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index2', 'MockeryDocs.tex', u'Mockery Docs Documentation',
('index', 'MockeryDocs.tex', u'Mockery Docs Documentation',
u'Pádraic Brady, Dave Marshall, Wouter, Graham Campbell', 'manual'),
]
@@ -217,7 +217,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index2', 'mockerydocs', u'Mockery Docs Documentation',
('index', 'mockerydocs', u'Mockery Docs Documentation',
[u'Pádraic Brady, Dave Marshall, Wouter, Graham Campbell'], 1)
]
@@ -231,7 +231,7 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index2', 'MockeryDocs', u'Mockery Docs Documentation',
('index', 'MockeryDocs', u'Mockery Docs Documentation',
u'Pádraic Brady, Dave Marshall, Wouter, Graham Campbell', 'MockeryDocs', 'One line description of project.',
'Miscellaneous'),
]
@@ -257,3 +257,11 @@ if not on_rtd: # only import and set the theme if we're building docs locally
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
print sphinx_rtd_theme.get_html_theme_path()
# load PhpLexer
from sphinx.highlighting import lexers
from pygments.lexers.web import PhpLexer
# enable highlighting for PHP code not between <?php ... ?> by default
lexers['php'] = PhpLexer(startinline=True)
lexers['php-annotations'] = PhpLexer(startinline=True)

View File

@@ -0,0 +1,52 @@
.. index::
single: Cookbook; Big Parent Class
Big Parent Class
================
In some application code, especially older legacy code, we can come across some
classes that extend a "big parent class" - a parent class that knows and does
too much:
.. code-block:: php
class BigParentClass
{
public function doesEverything()
{
// sets up database connections
// writes to log files
}
}
class ChildClass extends BigParentClass
{
public function doesOneThing()
{
// but calls on BigParentClass methods
$result = $this->doesEverything();
// does something with $result
return $result;
}
}
We want to test our ``ChildClass`` and its ``doesOneThing`` method, but the
problem is that it calls on ``BigParentClass::doesEverything()``. One way to
handle this would be to mock out **all** of the dependencies ``BigParentClass``
has and needs, and then finally actually test our ``doesOneThing`` method. It's
an awful lot of work to do that.
What we can do, is to do something... unconventional. We can create a runtime
partial test double of the ``ChildClass`` itself and mock only the parent's
``doesEverything()`` method:
.. code-block:: php
$childClass = \Mockery::mock('ChildClass')->makePartial();
$childClass->shouldReceive('doesEverything')
->andReturn('some result from parent');
$childClass->doesOneThing(); // string("some result from parent");
With this approach we mock out only the ``doesEverything()`` method, and all the
unmocked methods are called on the actual ``ChildClass`` instance.

View File

@@ -0,0 +1,183 @@
.. index::
single: Cookbook; Class Constants
Class Constants
===============
When creating a test double for a class, Mockery does not create stubs out of
any class constants defined in the class we are mocking. Sometimes though, the
non-existence of these class constants, setup of the test, and the application
code itself, it can lead to undesired behavior, and even a PHP error:
``PHP Fatal error: Uncaught Error: Undefined class constant 'FOO' in ...``
While supporting class constants in Mockery would be possible, it does require
an awful lot of work, for a small number of use cases.
Named Mocks
-----------
We can, however, deal with these constants in a way supported by Mockery - by
using :ref:`creating-test-doubles-named-mocks`.
A named mock is a test double that has a name of the class we want to mock, but
under it is a stubbed out class that mimics the real class with canned responses.
Lets look at the following made up, but not impossible scenario:
.. code-block:: php
class Fetcher
{
const SUCCESS = 0;
const FAILURE = 1;
public static function fetch()
{
// Fetcher gets something for us from somewhere...
return self::SUCCESS;
}
}
class MyClass
{
public function doFetching()
{
$response = Fetcher::fetch();
if ($response == Fetcher::SUCCESS) {
echo "Thanks!" . PHP_EOL;
} else {
echo "Try again!" . PHP_EOL;
}
}
}
Our ``MyClass`` calls a ``Fetcher`` that fetches some resource from somewhere -
maybe it downloads a file from a remote web service. Our ``MyClass`` prints out
a response message depending on the response from the ``Fetcher::fetch()`` call.
When testing ``MyClass`` we don't really want ``Fetcher`` to go and download
random stuff from the internet every time we run our test suite. So we mock it
out:
.. code-block:: php
// Using alias: because fetch is called statically!
\Mockery::mock('alias:Fetcher')
->shouldReceive('fetch')
->andReturn(0);
$myClass = new MyClass();
$myClass->doFetching();
If we run this, our test will error out with a nasty
``PHP Fatal error: Uncaught Error: Undefined class constant 'SUCCESS' in ..``.
Here's how a ``namedMock()`` can help us in a situation like this.
We create a stub for the ``Fetcher`` class, stubbing out the class constants,
and then use ``namedMock()`` to create a mock named ``Fetcher`` based on our
stub:
.. code-block:: php
class FetcherStub
{
const SUCCESS = 0;
const FAILURE = 1;
}
\Mockery::namedMock('Fetcher', 'FetcherStub')
->shouldReceive('fetch')
->andReturn(0);
$myClass = new MyClass();
$myClass->doFetching();
This works because under the hood, Mockery creates a class called ``Fetcher``
that extends ``FetcherStub``.
The same approach will work even if ``Fetcher::fetch()`` is not a static
dependency:
.. code-block:: php
class Fetcher
{
const SUCCESS = 0;
const FAILURE = 1;
public function fetch()
{
// Fetcher gets something for us from somewhere...
return self::SUCCESS;
}
}
class MyClass
{
public function doFetching($fetcher)
{
$response = $fetcher->fetch();
if ($response == Fetcher::SUCCESS) {
echo "Thanks!" . PHP_EOL;
} else {
echo "Try again!" . PHP_EOL;
}
}
}
And the test will have something like this:
.. code-block:: php
class FetcherStub
{
const SUCCESS = 0;
const FAILURE = 1;
}
$mock = \Mockery::mock('Fetcher', 'FetcherStub')
$mock->shouldReceive('fetch')
->andReturn(0);
$myClass = new MyClass();
$myClass->doFetching($mock);
Constants Map
-------------
Another way of mocking class constants can be with the use of the constants map configuration.
Given a class with constants:
.. code-block:: php
class Fetcher
{
const SUCCESS = 0;
const FAILURE = 1;
public function fetch()
{
// Fetcher gets something for us from somewhere...
return self::SUCCESS;
}
}
It can be mocked with:
.. code-block:: php
\Mockery::getConfiguration()->setConstantsMap([
'Fetcher' => [
'SUCCESS' => 'success',
'FAILURE' => 'fail',
]
]);
$mock = \Mockery::mock('Fetcher');
var_dump($mock::SUCCESS); // (string) 'success'
var_dump($mock::FAILURE); // (string) 'fail'

View File

@@ -6,6 +6,11 @@ Cookbook
default_expectations
detecting_mock_objects
not_calling_the_constructor
mocking_hard_dependencies
class_constants
big_parent_class
mockery_on
mocking_class_within_class
.. include:: map.rst.inc

View File

@@ -1,3 +1,7 @@
* :doc:`/cookbook/default_expectations`
* :doc:`/cookbook/detecting_mock_objects`
* :doc:`/cookbook/not_calling_the_constructor`
* :doc:`/cookbook/mocking_hard_dependencies`
* :doc:`/cookbook/class_constants`
* :doc:`/cookbook/big_parent_class`
* :doc:`/cookbook/mockery_on`

View File

@@ -0,0 +1,85 @@
.. index::
single: Cookbook; Complex Argument Matching With Mockery::on
Complex Argument Matching With Mockery::on
==========================================
When we need to do a more complex argument matching for an expected method call,
the ``\Mockery::on()`` matcher comes in really handy. It accepts a closure as an
argument and that closure in turn receives the argument passed in to the method,
when called. If the closure returns ``true``, Mockery will consider that the
argument has passed the expectation. If the closure returns ``false``, or a
"falsey" value, the expectation will not pass.
The ``\Mockery::on()`` matcher can be used in various scenarios — validating
an array argument based on multiple keys and values, complex string matching...
Say, for example, we have the following code. It doesn't do much; publishes a
post by setting the ``published`` flag in the database to ``1`` and sets the
``published_at`` to the current date and time:
.. code-block:: php
<?php
namespace Service;
class Post
{
public function __construct($model)
{
$this->model = $model;
}
public function publishPost($id)
{
$saveData = [
'post_id' => $id,
'published' => 1,
'published_at' => gmdate('Y-m-d H:i:s'),
];
$this->model->save($saveData);
}
}
In a test we would mock the model and set some expectations on the call of the
``save()`` method:
.. code-block:: php
<?php
$postId = 42;
$modelMock = \Mockery::mock('Model');
$modelMock->shouldReceive('save')
->once()
->with(\Mockery::on(function ($argument) use ($postId) {
$postIdIsSet = isset($argument['post_id']) && $argument['post_id'] === $postId;
$publishedFlagIsSet = isset($argument['published']) && $argument['published'] === 1;
$publishedAtIsSet = isset($argument['published_at']);
return $postIdIsSet && $publishedFlagIsSet && $publishedAtIsSet;
}));
$service = new \Service\Post($modelMock);
$service->publishPost($postId);
\Mockery::close();
The important part of the example is inside the closure we pass to the
``\Mockery::on()`` matcher. The ``$argument`` is actually the ``$saveData`` argument
the ``save()`` method gets when it is called. We check for a couple of things in
this argument:
* the post ID is set, and is same as the post ID we passed in to the
``publishPost()`` method,
* the ``published`` flag is set, and is ``1``, and
* the ``published_at`` key is present.
If any of these requirements is not satisfied, the closure will return ``false``,
the method call expectation will not be met, and Mockery will throw a
``NoMatchingExpectationException``.
.. note::
This cookbook entry is an adaption of the blog post titled
`"Complex argument matching in Mockery" <https://robertbasic.com/blog/complex-argument-matching-in-mockery/>`_,
published by Robert Basic on his blog.

View File

@@ -0,0 +1,146 @@
.. index::
single: Cookbook; Mocking class within class
.. _mocking-class-within-class:
Mocking class within class
==========================
Imagine a case where you need to create an instance of a class and use it
within the same method:
.. code-block:: php
// Point.php
<?php
namespace App;
class Point {
public function setPoint($x, $y) {
echo "Point (" . $x . ", " . $y . ")" . PHP_EOL;
}
}
// Rectangle.php
<?php
namespace App;
use App\Point;
class Rectangle {
public function create($x1, $y1, $x2, $y2) {
$a = new Point();
$a->setPoint($x1, $y1);
$b = new Point();
$b->setPoint($x2, $y1);
$c = new Point();
$c->setPoint($x2, $y2);
$d = new Point();
$d->setPoint($x1, $y2);
$this->draw([$a, $b, $c, $d]);
}
public function draw($points) {
echo "Do something with the points";
}
}
And that you want to test that a logic in ``Rectangle->create()`` calls
properly each used thing - in this case calls ``Point->setPoint()``, but
``Rectangle->draw()`` does some graphical stuff that you want to avoid calling.
You set the mocks for ``App\Point`` and ``App\Rectangle``:
.. code-block:: php
<?php
class MyTest extends PHPUnit\Framework\TestCase {
public function testCreate() {
$point = Mockery::mock("App\Point");
// check if our mock is called
$point->shouldReceive("setPoint")->andThrow(Exception::class);
$rect = Mockery::mock("App\Rectangle")->makePartial();
$rect->shouldReceive("draw");
$rect->create(0, 0, 100, 100); // does not throw exception
Mockery::close();
}
}
and the test does not work. Why? The mocking relies on the class not being
present yet, but the class is autoloaded therefore the mock alone for
``App\Point`` is useless which you can see with ``echo`` being executed.
Mocks however work for the first class in the order of loading i.e.
``App\Rectangle``, which loads the ``App\Point`` class. In more complex example
that would be a single point that initiates the whole loading (``use Class``)
such as::
A // main loading initiator
|- B // another loading initiator
| |-E
| +-G
|
|- C // another loading initiator
| +-F
|
+- D
That basically means that the loading prevents mocking and for each such
a loading initiator there needs to be implemented a workaround.
Overloading is one approach, however it pollutes the global state. In this case
we try to completely avoid the global state pollution with custom
``new Class()`` behavior per loading initiator and that can be mocked easily
in few critical places.
That being said, although we can't stop loading, we can return mocks. Let's
look at ``Rectangle->create()`` method:
.. code-block:: php
class Rectangle {
public function newPoint() {
return new Point();
}
public function create($x1, $y1, $x2, $y2) {
$a = $this->newPoint();
$a->setPoint($x1, $y1);
...
}
...
}
We create a custom function to encapsulate ``new`` keyword that would otherwise
just use the autoloaded class ``App\Point`` and in our test we mock that function
so that it returns our mock:
.. code-block:: php
<?php
class MyTest extends PHPUnit\Framework\TestCase {
public function testCreate() {
$point = Mockery::mock("App\Point");
// check if our mock is called
$point->shouldReceive("setPoint")->andThrow(Exception::class);
$rect = Mockery::mock("App\Rectangle")->makePartial();
$rect->shouldReceive("draw");
// pass the App\Point mock into App\Rectangle as an alternative
// to using new App\Point() in-place.
$rect->shouldReceive("newPoint")->andReturn($point);
$this->expectException(Exception::class);
$rect->create(0, 0, 100, 100);
Mockery::close();
}
}
If we run this test now, it should pass. For more complex cases we'd find
the next loader in the program flow and proceed with wrapping and passing
mock instances with predefined behavior into already existing classes.

View File

@@ -16,7 +16,7 @@ Let's take the following code for an example:
{
function callExternalService($param)
{
$externalService = new Service\External();
$externalService = new Service\External($version = 5);
$externalService->sendSomething($param);
return $externalService->getSomething();
}
@@ -91,3 +91,47 @@ Our test example from above now becomes:
$this->assertSame('Tested!', $result);
}
}
Testing the constructor arguments of hard Dependencies
------------------------------------------------------
Sometimes we might want to ensure that the hard dependency is instantiated with
particular arguments. With overloaded mocks, we can set up expectations on the
constructor.
.. code-block:: php
<?php
namespace AppTest;
use Mockery as m;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class ServiceTest extends \PHPUnit_Framework_TestCase
{
public function testCallingExternalService()
{
$externalMock = m::mock('overload:App\Service\External');
$externalMock->allows('sendSomething');
$externalMock->shouldReceive('__construct')
->once()
->with(5);
$service = new \App\Service();
$result = $service->callExternalService($param);
}
}
.. note::
For more straightforward and single-process tests oriented way check
:ref:`mocking-class-within-class`.
.. note::
This cookbook entry is an adaption of the blog post titled
`"Mocking hard dependencies with Mockery" <https://robertbasic.com/blog/mocking-hard-dependencies-with-mockery/>`_,
published by Robert Basic on his blog.

View File

@@ -0,0 +1,63 @@
.. index::
single: Cookbook; Not Calling the Original Constructor
Not Calling the Original Constructor
====================================
When creating generated partial test doubles, Mockery mocks out only the method
which we specifically told it to. This means that the original constructor of
the class we are mocking will be called.
In some cases this is not a desired behavior, as the constructor might issue
calls to other methods, or other object collaborators, and as such, can create
undesired side-effects in the application's environment when running the tests.
If this happens, we need to use runtime partial test doubles, as they don't
call the original constructor.
.. code-block:: php
class MyClass
{
public function __construct()
{
echo "Original constructor called." . PHP_EOL;
// Other side-effects can happen...
}
}
// This will print "Original constructor called."
$mock = \Mockery::mock('MyClass[foo]');
A better approach is to use runtime partial doubles:
.. code-block:: php
class MyClass
{
public function __construct()
{
echo "Original constructor called." . PHP_EOL;
// Other side-effects can happen...
}
}
// This will not print anything
$mock = \Mockery::mock('MyClass')->makePartial();
$mock->shouldReceive('foo');
This is one of the reason why we don't recommend using generated partial test
doubles, but if possible, always use the runtime partials.
Read more about :ref:`creating-test-doubles-partial-test-doubles`.
.. note::
The way generated partial test doubles work, is a BC break. If you use a
really old version of Mockery, it might behave in a way that the constructor
is not being called for these generated partials. In the case if you upgrade
to a more recent version of Mockery, you'll probably have to change your
tests to use runtime partials, instead of generated ones.
This change was introduced in early 2013, so it is highly unlikely that you
are using a Mockery from before that, so this should not be an issue.

View File

@@ -7,5 +7,6 @@ Getting Started
installation
upgrading
simple_example
quick_reference
.. include:: map.rst.inc

View File

@@ -4,8 +4,8 @@
Installation
============
Mockery can be installed using Composer, PEAR or by cloning it from its GitHub
repository. These three options are outlined below.
Mockery can be installed using Composer or by cloning it from its GitHub
repository. These two options are outlined below.
Composer
--------
@@ -35,17 +35,11 @@ To install, you then may call:
This will install Mockery as a development dependency, meaning it won't be
installed when using ``php composer.phar update --no-dev`` in production.
PEAR
----
Mockery is hosted on the `survivethedeepend.com <http://pear.survivethedeepend.com>`_
PEAR channel and can be installed using the following commands:
Other way to install is directly from composer command line, as below.
.. code-block:: bash
sudo pear channel-discover pear.survivethedeepend.com
sudo pear channel-discover hamcrest.googlecode.com/svn/pear
sudo pear install --alldeps deepend/Mockery
php composer.phar require --dev mockery/mockery
Git
---
@@ -53,16 +47,3 @@ Git
The Git repository hosts the development version in its master branch. You can
install this using Composer by referencing ``dev-master`` as your preferred
version in your project's ``composer.json`` file as the earlier example shows.
You may also install this development version using PEAR:
.. code-block:: bash
git clone git://github.com/padraic/mockery.git
cd mockery
sudo pear channel-discover hamcrest.googlecode.com/svn/pear
sudo pear install --alldeps package.xml
The above processes will install both Mockery and Hamcrest. While omitting
Hamcrest will not break Mockery, Hamcrest is recommended as it adds a wider
variety of functionality for argument matching.

View File

@@ -1,3 +1,4 @@
* :doc:`/getting_started/installation`
* :doc:`/getting_started/upgrading`
* :doc:`/getting_started/simple_example`
* :doc:`/getting_started/quick_reference`

View File

@@ -0,0 +1,200 @@
.. index::
single: Quick Reference
Quick Reference
===============
The purpose of this page is to give a quick and short overview of some of the
most common Mockery features.
Do read the :doc:`../reference/index` to learn about all the Mockery features.
Integrate Mockery with PHPUnit, either by extending the ``MockeryTestCase``:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class MyTest extends MockeryTestCase
{
}
or by using the ``MockeryPHPUnitIntegration`` trait:
.. code-block:: php
use \PHPUnit\Framework\TestCase;
use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
class MyTest extends TestCase
{
use MockeryPHPUnitIntegration;
}
Creating a test double:
.. code-block:: php
$testDouble = \Mockery::mock('MyClass');
Creating a test double that implements a certain interface:
.. code-block:: php
$testDouble = \Mockery::mock('MyClass, MyInterface');
Expecting a method to be called on a test double:
.. code-block:: php
$testDouble = \Mockery::mock('MyClass');
$testDouble->shouldReceive('foo');
Expecting a method to **not** be called on a test double:
.. code-block:: php
$testDouble = \Mockery::mock('MyClass');
$testDouble->shouldNotReceive('foo');
Expecting a method to be called on a test double, once, with a certain argument,
and to return a value:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->once()
->with($arg)
->andReturn($returnValue);
Expecting a method to be called on a test double and to return a different value
for each successive call:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->andReturn(1, 2, 3);
$mock->foo(); // int(1);
$mock->foo(); // int(2);
$mock->foo(); // int(3);
$mock->foo(); // int(3);
Creating a runtime partial test double:
.. code-block:: php
$mock = \Mockery::mock('MyClass')->makePartial();
Creating a spy:
.. code-block:: php
$spy = \Mockery::spy('MyClass');
Expecting that a spy should have received a method call:
.. code-block:: php
$spy = \Mockery::spy('MyClass');
$spy->foo();
$spy->shouldHaveReceived()->foo();
Not so simple examples
^^^^^^^^^^^^^^^^^^^^^^
Creating a mock object to return a sequence of values from a set of method
calls:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class SimpleTest extends MockeryTestCase
{
public function testSimpleMock()
{
$mock = \Mockery::mock(array('pi' => 3.1416, 'e' => 2.71));
$this->assertEquals(3.1416, $mock->pi());
$this->assertEquals(2.71, $mock->e());
}
}
Creating a mock object which returns a self-chaining Undefined object for a
method call:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class UndefinedTest extends MockeryTestCase
{
public function testUndefinedValues()
{
$mock = \Mockery::mock('mymock');
$mock->shouldReceive('divideBy')->with(0)->andReturnUndefined();
$this->assertTrue($mock->divideBy(0) instanceof \Mockery\Undefined);
}
}
Creating a mock object with multiple query calls and a single update call:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class DbTest extends MockeryTestCase
{
public function testDbAdapter()
{
$mock = \Mockery::mock('db');
$mock->shouldReceive('query')->andReturn(1, 2, 3);
$mock->shouldReceive('update')->with(5)->andReturn(NULL)->once();
// ... test code here using the mock
}
}
Expecting all queries to be executed before any updates:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class DbTest extends MockeryTestCase
{
public function testQueryAndUpdateOrder()
{
$mock = \Mockery::mock('db');
$mock->shouldReceive('query')->andReturn(1, 2, 3)->ordered();
$mock->shouldReceive('update')->andReturn(NULL)->once()->ordered();
// ... test code here using the mock
}
}
Creating a mock object where all queries occur after startup, but before finish,
and where queries are expected with several different params:
.. code-block:: php
use \Mockery\Adapter\Phpunit\MockeryTestCase;
class DbTest extends MockeryTestCase
{
public function testOrderedQueries()
{
$db = \Mockery::mock('db');
$db->shouldReceive('startup')->once()->ordered();
$db->shouldReceive('query')->with('CPWR')->andReturn(12.3)->once()->ordered('queries');
$db->shouldReceive('query')->with('MSFT')->andReturn(10.0)->once()->ordered('queries');
$db->shouldReceive('query')->with(\Mockery::pattern("/^....$/"))->andReturn(3.3)->atLeast()->once()->ordered('queries');
$db->shouldReceive('finish')->once()->ordered();
// ... test code here using the mock
}
}

View File

@@ -14,21 +14,21 @@ interaction with the ``Temperature`` class:
class Temperature
{
private $service;
public function __construct($service)
{
$this->_service = $service;
$this->service = $service;
}
public function average()
{
$total = 0;
for ($i=0;$i<3;$i++) {
$total += $this->_service->readTemp();
for ($i=0; $i<3; $i++) {
$total += $this->service->readTemp();
}
return $total/3;
}
}
Even without an actual service class, we can see how we expect it to operate.
@@ -38,28 +38,32 @@ mock object for the real service which allows us to test the behaviour of the
.. code-block:: php
use \Mockery as m;
use \Mockery;
class TemperatureTest extends PHPUnit_Framework_TestCase
class TemperatureTest extends \PHPUnit\Framework\TestCase
{
public function tearDown()
{
m::close();
Mockery::close();
}
public function testGetsAverageTemperatureFromThreeServiceReadings()
{
$service = m::mock('service');
$service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);
$service = Mockery::mock('service');
$service->shouldReceive('readTemp')
->times(3)
->andReturn(10, 12, 14);
$temperature = new Temperature($service);
$this->assertEquals(12, $temperature->average());
}
}
We create a mock object which our ``Temperature`` class will use and set some
expectations for that mock — that it should receive three calls to the ``readTemp``
method, and these calls will return 10, 12, and 14 as results.
.. note::
PHPUnit integration can remove the need for a ``tearDown()`` method. See

View File

@@ -4,11 +4,67 @@
Upgrading
=========
Upgrading to 1.0.0
------------------
Minimum PHP version
+++++++++++++++++++
As of Mockery 1.0.0 the minimum PHP version required is 5.6.
Using Mockery with PHPUnit
++++++++++++++++++++++++++
In the "old days", 0.9.x and older, the way Mockery was integrated with PHPUnit was
through a PHPUnit listener. That listener would in turn call the ``\Mockery::close()``
method for us.
As of 1.0.0, PHPUnit test cases where we want to use Mockery, should either use the
``\Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration`` trait, or extend the
``\Mockery\Adapter\Phpunit\MockeryTestCase`` test case. This will in turn call the
``\Mockery::close()`` method for us.
Read the documentation for a detailed overview of ":doc:`/reference/phpunit_integration`".
``\Mockery\Matcher\MustBe`` is deprecated
+++++++++++++++++++++++++++++++++++++++++
As of 1.0.0 the ``\Mockery\Matcher\MustBe`` matcher is deprecated and will be removed in
Mockery 2.0.0. We recommend instead to use the PHPUnit or Hamcrest equivalents of the
MustBe matcher.
``allows`` and ``expects``
++++++++++++++++++++++++++
As of 1.0.0, Mockery has two new methods to set up expectations: ``allows`` and ``expects``.
This means that these methods names are now "reserved" for Mockery, or in other words
classes you want to mock with Mockery, can't have methods called ``allows`` or ``expects``.
Read more in the documentation about this ":doc:`/reference/alternative_should_receive_syntax`".
No more implicit regex matching for string arguments
++++++++++++++++++++++++++++++++++++++++++++++++++++
When setting up string arguments in method expectations, Mockery 0.9.x and older, would try
to match arguments using a regular expression in a "last attempt" scenario.
As of 1.0.0, Mockery will no longer attempt to do this regex matching, but will only try
first the identical operator ``===``, and failing that, the equals operator ``==``.
If you want to match an argument using regular expressions, please use the new
``\Mockery\Matcher\Pattern`` matcher. Read more in the documentation about this
pattern matcher in the ":doc:`/reference/argument_validation`" section.
``andThrow`` ``\Throwable``
+++++++++++++++++++++++++++
As of 1.0.0, the ``andThrow`` can now throw any ``\Throwable``.
Upgrading to 0.9
----------------
The generator was completely rewritten, so any code with a deep integration to
mockery will need evaluating
mockery will need evaluating.
Upgrading to 0.8
----------------
@@ -18,7 +74,7 @@ Since the release of 0.8.0 the following behaviours were altered:
1. The ``shouldIgnoreMissing()`` behaviour optionally applied to mock objects
returned an instance of ``\Mockery\Undefined`` when methods called did not
match a known expectation. Since 0.8.0, this behaviour was switched to
returning ``null`` instead. You can restore the 0.7.2 behavour by using the
returning ``null`` instead. You can restore the 0.7.2 behaviour by using the
following:
.. code-block:: php

View File

@@ -50,6 +50,18 @@ framework.
.. include:: reference/map.rst.inc
Mockery
-------
Learn about Mockery's configuration, reserved method names, exceptions...
.. toctree::
:hidden:
mockery/index
.. include:: mockery/map.rst.inc
Cookbook
--------

View File

@@ -0,0 +1,94 @@
.. index::
single: Mockery; Configuration
Mockery Global Configuration
============================
To allow for a degree of fine-tuning, Mockery utilises a singleton
configuration object to store a small subset of core behaviours. The three
currently present include:
* Option to allow/disallow the mocking of methods which do not actually exist
fulfilled (i.e. unused)
* Setter/Getter for added a parameter map for internal PHP class methods
(``Reflection`` cannot detect these automatically)
* Option to drive if quick definitions should define a stub or a mock with
an 'at least once' expectation.
By default, the first behaviour is enabled. Of course, there are
situations where this can lead to unintended consequences. The mocking of
non-existent methods may allow mocks based on real classes/objects to fall out
of sync with the actual implementations, especially when some degree of
integration testing (testing of object wiring) is not being performed.
You may allow or disallow this behaviour (whether for whole test suites or
just select tests) by using the following call:
.. code-block:: php
\Mockery::getConfiguration()->allowMockingNonExistentMethods(bool);
Passing a true allows the behaviour, false disallows it. It takes effect
immediately until switched back. If the behaviour is detected when not allowed,
it will result in an Exception being thrown at that point. Note that disallowing
this behaviour should be carefully considered since it necessarily removes at
least some of Mockery's flexibility.
The other two methods are:
.. code-block:: php
\Mockery::getConfiguration()->setInternalClassMethodParamMap($class, $method, array $paramMap)
\Mockery::getConfiguration()->getInternalClassMethodParamMap($class, $method)
These are used to define parameters (i.e. the signature string of each) for the
methods of internal PHP classes (e.g. SPL, or PECL extension classes like
ext/mongo's MongoCollection. Reflection cannot analyse the parameters of internal
classes. Most of the time, you never need to do this. It's mainly needed where an
internal class method uses pass-by-reference for a parameter - you MUST in such
cases ensure the parameter signature includes the ``&`` symbol correctly as Mockery
won't correctly add it automatically for internal classes. Note that internal class
parameter overriding is not available in PHP 8. This is because incompatible
signatures have been reclassified as fatal errors.
Finally there is the possibility to change what a quick definition produces.
By default quick definitions create stubs but you can change this behaviour
by asking Mockery to use 'at least once' expectations.
.. code-block:: php
\Mockery::getConfiguration()->getQuickDefinitions()->shouldBeCalledAtLeastOnce(bool)
Passing a true allows the behaviour, false disallows it. It takes effect
immediately until switched back. By doing so you can avoid the proliferating of
quick definitions that accumulate overtime in your code since the test would
fail in case the 'at least once' expectation is not fulfilled.
Disabling reflection caching
----------------------------
Mockery heavily uses `"reflection" <https://secure.php.net/manual/en/book.reflection.php>`_
to do it's job. To speed up things, Mockery caches internally the information it
gathers via reflection. In some cases, this caching can cause problems.
The **only** known situation when this occurs is when PHPUnit's ``--static-backup`` option
is used. If you use ``--static-backup`` and you get an error that looks like the
following:
.. code-block:: php
Error: Internal error: Failed to retrieve the reflection object
We suggest turning off the reflection cache as so:
.. code-block:: php
\Mockery::getConfiguration()->disableReflectionCache();
Turning it back on can be done like so:
.. code-block:: php
\Mockery::getConfiguration()->enableReflectionCache();
In no other situation should you be required turn this reflection cache off.

View File

@@ -14,21 +14,15 @@ so it can be documented and resolved where possible. Here is a list to note:
expectations set for it. This is necessary since Mockery must serialize and
unserialize objects to avoid some ``__construct()`` insanity and attempting
to mock a ``__wakeup()`` method as normal leads to a
``BadMethodCallException`` been thrown.
``BadMethodCallException`` being thrown.
2. Classes using non-real methods, i.e. where a method call triggers a
``__call()`` method, will throw an exception that the non-real method does
not exist unless you first define at least one expectation (a simple
``shouldReceive()`` call would suffice). This is necessary since there is
no other way for Mockery to be aware of the method name.
3. Mockery has two scenarios where real classes are replaced: Instance mocks
2. Mockery has two scenarios where real classes are replaced: Instance mocks
and alias mocks. Both will generate PHP fatal errors if the real class is
loaded, usually via a require or include statement. Only use these two mock
types where autoloading is in place and where classes are not explicitly
loaded on a per-file basis using ``require()``, ``require_once()``, etc.
4. Internal PHP classes are not entirely capable of being fully analysed using
3. Internal PHP classes are not entirely capable of being fully analysed using
``Reflection``. For example, ``Reflection`` cannot reveal details of
expected parameters to the methods of such internal classes. As a result,
there will be problems where a method parameter is defined to accept a
@@ -36,6 +30,14 @@ so it can be documented and resolved where possible. Here is a list to note:
pass by value on scalars and arrays). If references as internal class
method parameters are needed, you should use the
``\Mockery\Configuration::setInternalClassMethodParamMap()`` method.
Note, however that internal class parameter overriding is not available in
PHP 8 since incompatible signatures have been reclassified as fatal errors.
4. Creating a mock implementing a certain interface with incorrect case in the
interface name, and then creating a second mock implementing the same
interface, but this time with the correct case, will have undefined behavior
due to PHP's ``class_exists`` and related functions being case insensitive.
Using the ``::class`` keyword in PHP can help you avoid these mistakes.
The gotchas noted above are largely down to PHP's architecture and are assumed
to be unavoidable. But - if you figure out a solution (or a better one than

View File

@@ -2,9 +2,11 @@ Mockery
=======
.. toctree::
:maxdepth: 2
:hidden:
configuration
exceptions
reserved_method_names
gotchas
.. include:: map.rst.inc

View File

@@ -0,0 +1,4 @@
* :doc:`/mockery/configuration`
* :doc:`/mockery/exceptions`
* :doc:`/mockery/reserved_method_names`
* :doc:`/mockery/gotchas`

View File

@@ -12,7 +12,20 @@ name collision (reported as a PHP fatal error). The methods reserved by
Mockery are:
* ``shouldReceive()``
* ``shouldBeStrict()``
* ``shouldNotReceive()``
* ``allows()``
* ``expects()``
* ``shouldAllowMockingMethod()``
* ``shouldIgnoreMissing()``
* ``asUndefined()``
* ``shouldAllowMockingProtectedMethods()``
* ``makePartial()``
* ``byDefault()``
* ``shouldHaveReceived()``
* ``shouldHaveBeenCalled()``
* ``shouldNotHaveReceived()``
* ``shouldNotHaveBeenCalled()``
In addition, all mocks utilise a set of added methods and protected properties
which cannot exist on the class or object being mocked. These are far less

View File

@@ -0,0 +1,91 @@
.. index::
single: Alternative shouldReceive Syntax
Alternative shouldReceive Syntax
================================
As of Mockery 1.0.0, we support calling methods as we would call any PHP method,
and not as string arguments to Mockery ``should*`` methods.
The two Mockery methods that enable this are ``allows()`` and ``expects()``.
Allows
------
We use ``allows()`` when we create stubs for methods that return a predefined
return value, but for these method stubs we don't care how many times, or if at
all, were they called.
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->allows([
'name_of_method_1' => 'return value',
'name_of_method_2' => 'return value',
]);
This is equivalent with the following ``shouldReceive`` syntax:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive([
'name_of_method_1' => 'return value',
'name_of_method_2' => 'return value',
]);
Note that with this format, we also tell Mockery that we don't care about the
arguments to the stubbed methods.
If we do care about the arguments, we would do it like so:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->allows()
->name_of_method_1($arg1)
->andReturn('return value');
This is equivalent with the following ``shouldReceive`` syntax:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method_1')
->with($arg1)
->andReturn('return value');
Expects
-------
We use ``expects()`` when we want to verify that a particular method was called:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->expects()
->name_of_method_1($arg1)
->andReturn('return value');
This is equivalent with the following ``shouldReceive`` syntax:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method_1')
->once()
->with($arg1)
->andReturn('return value');
By default ``expects()`` sets up an expectation that the method should be called
once and once only. If we expect more than one call to the method, we can change
that expectation:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->expects()
->name_of_method_1($arg1)
->twice()
->andReturn('return value');

View File

@@ -6,7 +6,7 @@ Argument Validation
The arguments passed to the ``with()`` declaration when setting up an
expectation determine the criteria for matching method calls to expectations.
Thus, you can setup up many expectations for a single method, each
Thus, we can setup up many expectations for a single method, each
differentiated by the expected arguments. Such argument matching is done on a
"best fit" basis. This ensures explicit matches take precedence over
generalised matches.
@@ -21,148 +21,318 @@ arguments be defined in non-explicit terms, e.g. ``Mockery::any()`` passed to
Mockery's generic matchers do not cover all possibilities but offers optional
support for the Hamcrest library of matchers. Hamcrest is a PHP port of the
similarly named Java library (which has been ported also to Python, Erlang,
etc). I strongly recommend using Hamcrest since Mockery simply does not need
to duplicate Hamcrest's already impressive utility which itself promotes a
natural English DSL.
etc). By using Hamcrest, Mockery does not need to duplicate Hamcrest's already
impressive utility which itself promotes a natural English DSL.
The example below show Mockery matchers and their Hamcrest equivalent.
Hamcrest uses functions (no namespacing).
The examples below show Mockery matchers and their Hamcrest equivalent, if there
is one. Hamcrest uses functions (no namespacing).
Here's a sample of the possibilities.
.. note::
If you don't wish to use the global Hamcrest functions, they are all exposed
through the ``\Hamcrest\Matchers`` class as well, as static methods. Thus,
``identicalTo($arg)`` is the same as ``\Hamcrest\Matchers::identicalTo($arg)``
The most common matcher is the ``with()`` matcher:
.. code-block:: php
with(1)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(1):
Matches the integer ``1``. This passes the ``===`` test (identical). It does
facilitate a less strict ``==`` check (equals) where the string ``'1'`` would
also match the
argument.
It tells mockery that it should receive a call to the ``foo`` method with the
integer ``1`` as an argument. In cases like this, Mockery first tries to match
the arguments using ``===`` (identical) comparison operator. If the argument is
a primitive, and if it fails the identical comparison, Mockery does a fallback
to the ``==`` (equals) comparison operator.
When matching objects as arguments, Mockery only does the strict ``===``
comparison, which means only the same ``$object`` will match:
.. code-block:: php
with(\Mockery::any()) OR with(anything())
$object = new stdClass();
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with($object);
Matches any argument. Basically, anything and everything passed in this
argument slot is passed unconstrained.
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(identicalTo($object));
A different instance of ``stdClass`` will **not** match.
.. note::
The ``Mockery\Matcher\MustBe`` matcher has been deprecated.
If we need a loose comparison of objects, we can do that using Hamcrest's
``equalTo`` matcher:
.. code-block:: php
with(\Mockery::type('resource')) OR with(resourceValue()) OR with(typeOf('resource'))
$mock->shouldReceive("foo")
->with(equalTo(new stdClass));
Matches any resource, i.e. returns true from an ``is_resource()`` call. The
Type matcher accepts any string which can be attached to ``is_`` to form a
valid type check. For example, ``\Mockery::type('float')`` or Hamcrest's
``floatValue()`` and ``typeOf('float')`` checks using ``is_float()``, and
``\Mockery::type('callable')`` or Hamcrest's ``callable()`` uses
In cases when we don't care about the type, or the value of an argument, just
that any argument is present, we use ``any()``:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::any());
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(anything())
Anything and everything passed in this argument slot is passed unconstrained.
Validating Types and Resources
------------------------------
The ``type()`` matcher accepts any string which can be attached to ``is_`` to
form a valid type check.
To match any PHP resource, we could do the following:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::type('resource'));
// Hamcrest equivalents
$mock->shouldReceive("foo")
->with(resourceValue());
$mock->shouldReceive("foo")
->with(typeOf('resource'));
It will return a ``true`` from an ``is_resource()`` call, if the provided
argument to the method is a PHP resource. For example, ``\Mockery::type('float')``
or Hamcrest's ``floatValue()`` and ``typeOf('float')`` checks use ``is_float()``,
and ``\Mockery::type('callable')`` or Hamcrest's ``callable()`` uses
``is_callable()``.
The Type matcher also accepts a class or interface name to be used in an
``instanceof`` evaluation of the actual argument (similarly Hamcrest uses
``anInstanceOf()``).
The ``type()`` matcher also accepts a class or interface name to be used in an
``instanceof`` evaluation of the actual argument. Hamcrest uses ``anInstanceOf()``.
You may find a full list of the available type checkers at
A full list of the type checkers is available at
`php.net <http://www.php.net/manual/en/ref.var.php>`_ or browse Hamcrest's function
list in
`the Hamcrest code <http://code.google.com/p/hamcrest/source/browse/trunk/hamcrest-php/hamcrest/Hamcrest.php>`_.
`the Hamcrest code <https://github.com/hamcrest/hamcrest-php/blob/master/hamcrest/Hamcrest.php>`_.
.. _argument-validation-complex-argument-validation:
Complex Argument Validation
---------------------------
If we want to perform a complex argument validation, the ``on()`` matcher is
invaluable. It accepts a closure (anonymous function) to which the actual
argument will be passed.
.. code-block:: php
with(\Mockery::on(closure))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::on(closure));
The On matcher accepts a closure (anonymous function) to which the actual
argument will be passed. If the closure evaluates to (i.e. returns) boolean
``true`` then the argument is assumed to have matched the expectation. This is
invaluable where your argument expectation is a bit too complex for or simply
not implemented in the current default matchers.
There is no Hamcrest version of this functionality.
If the closure evaluates to (i.e. returns) boolean ``true`` then the argument is
assumed to have matched the expectation.
.. code-block:: php
with('/^foo/') OR with(matchesPattern('/^foo/'))
$mock = \Mockery::mock('MyClass');
The argument declarator also assumes any given string may be a regular
expression to be used against actual arguments when matching. The regex option
is only used when a) there is no ``===`` or ``==`` match and b) when the regex
is verified to be a valid regex (i.e. does not return false from
``preg_match()``). If the regex detection doesn't suit your tastes, Hamcrest
offers the more explicit ``matchesPattern()`` function.
$mock->shouldReceive('foo')
->with(\Mockery::on(function ($argument) {
if ($argument % 2 == 0) {
return true;
}
return false;
}));
$mock->foo(4); // matches the expectation
$mock->foo(3); // throws a NoMatchingExpectationException
.. note::
There is no Hamcrest version of the ``on()`` matcher.
We can also perform argument validation by passing a closure to ``withArgs()``
method. The closure will receive all arguments passed in the call to the expected
method and if it evaluates (i.e. returns) to boolean ``true``, then the list of
arguments is assumed to have matched the expectation:
.. code-block:: php
with(\Mockery::ducktype('foo', 'bar'))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->withArgs(closure);
The Ducktype matcher is an alternative to matching by class type. It simply
matches any argument which is an object containing the provided list of
The closure can also handle optional parameters, so if an optional parameter is
missing in the call to the expected method, it doesn't necessary means that the
list of arguments doesn't match the expectation.
.. code-block:: php
$closure = function ($odd, $even, $sum = null) {
$result = ($odd % 2 != 0) && ($even % 2 == 0);
if (!is_null($sum)) {
return $result && ($odd + $even == $sum);
}
return $result;
};
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->withArgs($closure);
$mock->foo(1, 2); // It matches the expectation: the optional argument is not needed
$mock->foo(1, 2, 3); // It also matches the expectation: the optional argument pass the validation
$mock->foo(1, 2, 4); // It doesn't match the expectation: the optional doesn't pass the validation
.. note::
In previous versions, Mockery's ``with()`` would attempt to do a pattern
matching against the arguments, attempting to use the argument as a
regular expression. Over time this proved to be not such a great idea, so
we removed this functionality, and have introduced ``Mockery::pattern()``
instead.
If we would like to match an argument against a regular expression, we can use
the ``\Mockery::pattern()``:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::pattern('/^foo/'));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(matchesPattern('/^foo/'));
The ``ducktype()`` matcher is an alternative to matching by class type:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::ducktype('foo', 'bar'));
It matches any argument which is an object containing the provided list of
methods to call.
There is no Hamcrest version of this functionality.
.. note::
There is no Hamcrest version of the ``ducktype()`` matcher.
Capturing Arguments
-------------------
If we want to perform multiple validations on a single argument, the ``capture``
matcher provides a streamlined alternative to using the ``on()`` matcher.
It accepts a variable which the actual argument will be assigned.
.. code-block:: php
with(\Mockery::mustBe(2)) OR with(identicalTo(2))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::capture($bar));
The MustBe matcher is more strict than the default argument matcher. The
default matcher allows for PHP type casting, but the MustBe matcher also
verifies that the argument must be of the same type as the expected value.
Thus by default, the argument `'2'` matches the actual argument 2 (integer)
but the MustBe matcher would fail in the same situation since the expected
argument was a string and instead we got an integer.
This will assign *any* argument passed to ``foo`` to the local ``$bar`` variable to
then perform additional validation using assertions.
Note: Objects are not subject to an identical comparison using this matcher
since PHP would fail the comparison if both objects were not the exact same
instance. This is a hindrance when objects are generated prior to being
returned, since an identical match just would never be possible.
.. note::
The ``capture`` matcher always evaluates to ``true``. As such, we should always
perform additional argument validation.
Additional Argument Matchers
----------------------------
The ``not()`` matcher matches any argument which is not equal or identical to
the matcher's parameter:
.. code-block:: php
with(\Mockery::not(2)) OR with(not(2))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::not(2));
The Not matcher matches any argument which is not equal or identical to the
matcher's parameter.
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(not(2));
``anyOf()`` matches any argument which equals any one of the given parameters:
.. code-block:: php
with(\Mockery::anyOf(1, 2)) OR with(anyOf(1,2))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::anyOf(1, 2));
Matches any argument which equals any one of the given parameters.
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(anyOf(1,2));
``notAnyOf()`` matches any argument which is not equal or identical to any of
the given parameters:
.. code-block:: php
with(\Mockery::notAnyOf(1, 2))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::notAnyOf(1, 2));
Matches any argument which is not equal or identical to any of the given
parameters.
.. note::
There is no Hamcrest version of this functionality.
There is no Hamcrest version of the ``notAnyOf()`` matcher.
``subset()`` matches any argument which is any array containing the given array
subset:
.. code-block:: php
with(\Mockery::subset(array(0 => 'foo')))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::subset(array(0 => 'foo')));
Matches any argument which is any array containing the given array subset.
This enforces both key naming and values, i.e. both the key and value of each
actual element is compared.
There is no Hamcrest version of this functionality, though Hamcrest can check
a single entry using ``hasEntry()`` or ``hasKeyValuePair()``.
.. note::
There is no Hamcrest version of this functionality, though Hamcrest can check
a single entry using ``hasEntry()`` or ``hasKeyValuePair()``.
``contains()`` matches any argument which is an array containing the listed
values:
.. code-block:: php
with(\Mockery::contains(value1, value2))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::contains(value1, value2));
Matches any argument which is an array containing the listed values. The
naming of keys is ignored.
The naming of keys is ignored.
``hasKey()`` matches any argument which is an array containing the given key
name:
.. code-block:: php
with(\Mockery::hasKey(key));
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasKey(key));
Matches any argument which is an array containing the given key name.
``hasValue()`` matches any argument which is an array containing the given
value:
.. code-block:: php
with(\Mockery::hasValue(value));
Matches any argument which is an array containing the given value.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasValue(value));

View File

@@ -0,0 +1,435 @@
.. index::
single: Reference; Creating Test Doubles
Creating Test Doubles
=====================
Mockery's main goal is to help us create test doubles. It can create stubs,
mocks, and spies.
Stubs and mocks are created the same. The difference between the two is that a
stub only returns a preset result when called, while a mock needs to have
expectations set on the method calls it expects to receive.
Spies are a type of test doubles that keep track of the calls they received, and
allow us to inspect these calls after the fact.
When creating a test double object, we can pass in an identifier as a name for
our test double. If we pass it no identifier, the test double name will be
unknown. Furthermore, the identifier does not have to be a class name. It is a
good practice, and our recommendation, to always name the test doubles with the
same name as the underlying class we are creating test doubles for.
If the identifier we use for our test double is a name of an existing class,
the test double will inherit the type of the class (via inheritance), i.e. the
mock object will pass type hints or ``instanceof`` evaluations for the existing
class. This is useful when a test double must be of a specific type, to satisfy
the expectations our code has.
Stubs and mocks
---------------
Stubs and mocks are created by calling the ``\Mockery::mock()`` method. The
following example shows how to create a stub, or a mock, object named "foo":
.. code-block:: php
$mock = \Mockery::mock('foo');
The mock object created like this is the loosest form of mocks possible, and is
an instance of ``\Mockery\MockInterface``.
.. note::
All test doubles created with Mockery are an instance of
``\Mockery\MockInterface``, regardless are they a stub, mock or a spy.
To create a stub or a mock object with no name, we can call the ``mock()``
method with no parameters:
.. code-block:: php
$mock = \Mockery::mock();
As we stated earlier, we don't recommend creating stub or mock objects without
a name.
Classes, abstracts, interfaces
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The recommended way to create a stub or a mock object is by using a name of
an existing class we want to create a test double of:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
This stub or mock object will have the type of ``MyClass``, through inheritance.
Stub or mock objects can be based on any concrete class, abstract class or even
an interface. The primary purpose is to ensure the mock object inherits a
specific type for type hinting.
.. code-block:: php
$mock = \Mockery::mock('MyInterface');
This stub or mock object will implement the ``MyInterface`` interface.
.. note::
Classes marked final, or classes that have methods marked final cannot be
mocked fully. Mockery supports creating partial mocks for these cases.
Partial mocks will be explained later in the documentation.
Mockery also supports creating stub or mock objects based on a single existing
class, which must implement one or more interfaces. We can do this by providing
a comma-separated list of the class and interfaces as the first argument to the
``\Mockery::mock()`` method:
.. code-block:: php
$mock = \Mockery::mock('MyClass, MyInterface, OtherInterface');
This stub or mock object will now be of type ``MyClass`` and implement the
``MyInterface`` and ``OtherInterface`` interfaces.
.. note::
The class name doesn't need to be the first member of the list but it's a
friendly convention to use for readability.
We can tell a mock to implement the desired interfaces by passing the list of
interfaces as the second argument:
.. code-block:: php
$mock = \Mockery::mock('MyClass', 'MyInterface, OtherInterface');
For all intents and purposes, this is the same as the previous example.
Spies
-----
The third type of test doubles Mockery supports are spies. The main difference
between spies and mock objects is that with spies we verify the calls made
against our test double after the calls were made. We would use a spy when we
don't necessarily care about all of the calls that are going to be made to an
object.
A spy will return ``null`` for all method calls it receives. It is not possible
to tell a spy what will be the return value of a method call. If we do that, then
we would deal with a mock object, and not with a spy.
We create a spy by calling the ``\Mockery::spy()`` method:
.. code-block:: php
$spy = \Mockery::spy('MyClass');
Just as with stubs or mocks, we can tell Mockery to base a spy on any concrete
or abstract class, or to implement any number of interfaces:
.. code-block:: php
$spy = \Mockery::spy('MyClass, MyInterface, OtherInterface');
This spy will now be of type ``MyClass`` and implement the ``MyInterface`` and
``OtherInterface`` interfaces.
.. note::
The ``\Mockery::spy()`` method call is actually a shorthand for calling
``\Mockery::mock()->shouldIgnoreMissing()``. The ``shouldIgnoreMissing``
method is a "behaviour modifier". We'll discuss them a bit later.
Mocks vs. Spies
---------------
Let's try and illustrate the difference between mocks and spies with the
following example:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$spy = \Mockery::spy('MyClass');
$mock->shouldReceive('foo')->andReturn(42);
$mockResult = $mock->foo();
$spyResult = $spy->foo();
$spy->shouldHaveReceived()->foo();
var_dump($mockResult); // int(42)
var_dump($spyResult); // null
As we can see from this example, with a mock object we set the call expectations
before the call itself, and we get the return result we expect it to return.
With a spy object on the other hand, we verify the call has happened after the
fact. The return result of a method call against a spy is always ``null``.
We also have a dedicated chapter to :doc:`spies` only.
.. _creating-test-doubles-partial-test-doubles:
Partial Test Doubles
--------------------
Partial doubles are useful when we want to stub out, set expectations for, or
spy on *some* methods of a class, but run the actual code for other methods.
We differentiate between three types of partial test doubles:
* runtime partial test doubles,
* generated partial test doubles, and
* proxied partial test doubles.
Runtime partial test doubles
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
What we call a runtime partial, involves creating a test double and then telling
it to make itself partial. Any method calls that the double hasn't been told to
allow or expect, will act as they would on a normal instance of the object.
.. code-block:: php
class Foo {
function foo() { return 123; }
function bar() { return $this->foo(); }
}
$foo = mock(Foo::class)->makePartial();
$foo->foo(); // int(123);
We can then tell the test double to allow or expect calls as with any other
Mockery double.
.. code-block:: php
$foo->shouldReceive('foo')->andReturn(456);
$foo->bar(); // int(456)
See the cookbook entry on :doc:`../cookbook/big_parent_class` for an example
usage of runtime partial test doubles.
Generated partial test doubles
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The second type of partial double we can create is what we call a generated
partial. With generated partials, we specifically tell Mockery which methods
we want to be able to allow or expect calls to. All other methods will run the
actual code *directly*, so stubs and expectations on these methods will not
work.
.. code-block:: php
class Foo {
function foo() { return 123; }
function bar() { return $this->foo(); }
}
$foo = mock("Foo[foo]");
$foo->foo(); // error, no expectation set
$foo->shouldReceive('foo')->andReturn(456);
$foo->foo(); // int(456)
// setting an expectation for this has no effect
$foo->shouldReceive('bar')->andReturn(999);
$foo->bar(); // int(456)
It's also possible to specify explicitly which methods to run directly using
the `!method` syntax:
.. code-block:: php
class Foo {
function foo() { return 123; }
function bar() { return $this->foo(); }
}
$foo = mock("Foo[!foo]");
$foo->foo(); // int(123)
$foo->bar(); // error, no expectation set
.. note::
Even though we support generated partial test doubles, we do not recommend
using them.
One of the reasons why is because a generated partial will call the original
constructor of the mocked class. This can have unwanted side-effects during
testing application code.
See :doc:`../cookbook/not_calling_the_constructor` for more details.
Proxied partial test doubles
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A proxied partial mock is a partial of last resort. We may encounter a class
which is simply not capable of being mocked because it has been marked as
final. Similarly, we may find a class with methods marked as final. In such a
scenario, we cannot simply extend the class and override methods to mock - we
need to get creative.
.. code-block:: php
$mock = \Mockery::mock(new MyClass);
Yes, the new mock is a Proxy. It intercepts calls and reroutes them to the
proxied object (which we construct and pass in) for methods which are not
subject to any expectations. Indirectly, this allows us to mock methods
marked final since the Proxy is not subject to those limitations. The tradeoff
should be obvious - a proxied partial will fail any typehint checks for the
class being mocked since it cannot extend that class.
.. _creating-test-doubles-aliasing:
Aliasing
--------
Prefixing the valid name of a class (which is NOT currently loaded) with
"alias:" will generate an "alias mock". Alias mocks create a class alias with
the given classname to stdClass and are generally used to enable the mocking
of public static methods. Expectations set on the new mock object which refer
to static methods will be used by all static calls to this class.
.. code-block:: php
$mock = \Mockery::mock('alias:MyClass');
.. note::
Even though aliasing classes is supported, we do not recommend it.
Overloading
-----------
Prefixing the valid name of a class (which is NOT currently loaded) with
"overload:" will generate an alias mock (as with "alias:") except that created
new instances of that class will import any expectations set on the origin
mock (``$mock``). The origin mock is never verified since it's used an
expectation store for new instances. For this purpose we use the term "instance
mock" to differentiate it from the simpler "alias mock".
In other words, an instance mock will "intercept" when a new instance of the
mocked class is created, then the mock will be used instead. This is useful
especially when mocking hard dependencies which will be discussed later.
.. code-block:: php
$mock = \Mockery::mock('overload:MyClass');
.. note::
Using alias/instance mocks across more than one test will generate a fatal
error since we can't have two classes of the same name. To avoid this,
run each test of this kind in a separate PHP process (which is supported
out of the box by both PHPUnit and PHPT).
.. _creating-test-doubles-named-mocks:
Named Mocks
-----------
The ``namedMock()`` method will generate a class called by the first argument,
so in this example ``MyClassName``. The rest of the arguments are treated in the
same way as the ``mock`` method:
.. code-block:: php
$mock = \Mockery::namedMock('MyClassName', 'DateTime');
This example would create a class called ``MyClassName`` that extends
``DateTime``.
Named mocks are quite an edge case, but they can be useful when code depends
on the ``__CLASS__`` magic constant, or when we need two derivatives of an
abstract type, that are actually different classes.
See the cookbook entry on :doc:`../cookbook/class_constants` for an example
usage of named mocks.
.. note::
We can only create a named mock once, any subsequent calls to
``namedMock``, with different arguments are likely to cause exceptions.
.. _creating-test-doubles-constructor-arguments:
Constructor Arguments
---------------------
Sometimes the mocked class has required constructor arguments. We can pass these
to Mockery as an indexed array, as the 2nd argument:
.. code-block:: php
$mock = \Mockery::mock('MyClass', [$constructorArg1, $constructorArg2]);
or if we need the ``MyClass`` to implement an interface as well, as the 3rd
argument:
.. code-block:: php
$mock = \Mockery::mock('MyClass', 'MyInterface', [$constructorArg1, $constructorArg2]);
Mockery now knows to pass in ``$constructorArg1`` and ``$constructorArg2`` as
arguments to the constructor.
.. _creating-test-doubles-behavior-modifiers:
Behavior Modifiers
------------------
When creating a mock object, we may wish to use some commonly preferred
behaviours that are not the default in Mockery.
The use of the ``shouldIgnoreMissing()`` behaviour modifier will label this
mock object as a Passive Mock:
.. code-block:: php
\Mockery::mock('MyClass')->shouldIgnoreMissing();
In such a mock object, calls to methods which are not covered by expectations
will return ``null`` instead of the usual error about there being no expectation
matching the call.
On PHP >= 7.0.0, methods with missing expectations that have a return type
will return either a mock of the object (if return type is a class) or a
"falsy" primitive value, e.g. empty string, empty array, zero for ints and
floats, false for bools, or empty closures.
On PHP >= 7.1.0, methods with missing expectations and nullable return type
will return null.
We can optionally prefer to return an object of type ``\Mockery\Undefined``
(i.e. a ``null`` object) (which was the 0.7.2 behaviour) by using an
additional modifier:
.. code-block:: php
\Mockery::mock('MyClass')->shouldIgnoreMissing()->asUndefined();
The returned object is nothing more than a placeholder so if, by some act of
fate, it's erroneously used somewhere it shouldn't it will likely not pass a
logic check.
We have encountered the ``makePartial()`` method before, as it is the method we
use to create runtime partial test doubles:
.. code-block:: php
\Mockery::mock('MyClass')->makePartial();
This form of mock object will defer all methods not subject to an expectation to
the parent class of the mock, i.e. ``MyClass``. Whereas the previous
``shouldIgnoreMissing()`` returned ``null``, this behaviour simply calls the
parent's matching method.

View File

@@ -12,13 +12,13 @@ similar to:
$object->foo()->bar()->zebra()->alpha()->selfDestruct();
The long chain of method calls isn't necessarily a bad thing, assuming they
each link back to a local object the calling class knows. Just as a fun
example, Mockery's long chains (after the first ``shouldReceive()`` method)
all call to the same instance of ``\Mockery\Expectation``. However, sometimes
this is not the case and the chain is constantly crossing object boundaries.
each link back to a local object the calling class knows. As a fun example,
Mockery's long chains (after the first ``shouldReceive()`` method) all call to
the same instance of ``\Mockery\Expectation``. However, sometimes this is not
the case and the chain is constantly crossing object boundaries.
In either case, mocking such a chain can be a horrible task. To make it easier
Mockery support demeter chain mocking. Essentially, we shortcut through the
Mockery supports demeter chain mocking. Essentially, we shortcut through the
chain and return a defined value from the final call. For example, let's
assume ``selfDestruct()`` returns the string "Ten!" to $object (an instance of
``CaptainsConsole``). Here's how we could mock it.

View File

@@ -4,256 +4,529 @@
Expectation Declarations
========================
Once you have created a mock object, you'll often want to start defining how
.. note::
In order for our expectations to work we MUST call ``Mockery::close()``,
preferably in a callback method such as ``tearDown`` or ``_after``
(depending on whether or not we're integrating Mockery with another
framework). This static call cleans up the Mockery container used by the
current test, and run any verification tasks needed for our expectations.
Once we have created a mock object, we'll often want to start defining how
exactly it should behave (and how it should be called). This is where the
Mockery expectation declarations take over.
.. code-block:: php
Declaring Method Call Expectations
----------------------------------
shouldReceive(method_name)
Declares that the mock expects a call to the given method name. This is the
starting expectation upon which all other expectations and constraints are
appended.
To tell our test double to expect a call for a method with a given name, we use
the ``shouldReceive`` method:
.. code-block:: php
shouldReceive(method1, method2, ...)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method');
Declares a number of expected method calls, all of which will adopt any
chained expectations or constraints.
This is the starting expectation upon which all other expectations and
constraints are appended.
We can declare more than one method call to be expected:
.. code-block:: php
shouldReceive(array('method1'=>1, 'method2'=>2, ...))
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method_1', 'name_of_method_2');
Declares a number of expected calls but also their return values. All will
adopt any additional chained expectations or constraints.
All of these will adopt any chained expectations or constraints.
It is possible to declare the expectations for the method calls, along with
their return values:
.. code-block:: php
shouldReceive(closure)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive([
'name_of_method_1' => 'return value 1',
'name_of_method_2' => 'return value 2',
]);
Creates a mock object (only from a partial mock) which is used to create a
mock object recorder. The recorder is a simple proxy to the original object
passed in for mocking. This is passed to the closure, which may run it through
a set of operations which are recorded as expectations on the partial mock. A
simple use case is automatically recording expectations based on an existing
usage (e.g. during refactoring). See examples in a later section.
There's also a shorthand way of setting up method call expectations and their
return values:
.. code-block:: php
shouldNotReceive(method_name)
$mock = \Mockery::mock('MyClass', ['name_of_method_1' => 'return value 1', 'name_of_method_2' => 'return value 2']);
Declares that the mock should not expect a call to the given method name. This
method is a convenience method for calling ``shouldReceive()->never()``.
All of these will adopt any additional chained expectations or constraints.
We can declare that a test double should not expect a call to the given method
name:
.. code-block:: php
with(arg1, arg2, ...) / withArgs(array(arg1, arg2, ...))
$mock = \Mockery::mock('MyClass');
$mock->shouldNotReceive('name_of_method');
Adds a constraint that this expectation only applies to method calls which
match the expected argument list. You can add a lot more flexibility to
argument matching using the built in matcher classes (see later). For example,
``\Mockery::any()`` matches any argument passed to that position in the
``with()`` parameter list. Mockery also allows Hamcrest library matchers - for
example, the Hamcrest function ``anything()`` is equivalent to
``\Mockery::any()``.
This method is a convenience method for calling ``shouldReceive()->never()``.
Declaring Method Argument Expectations
--------------------------------------
For every method we declare expectation for, we can add constraints that the
defined expectations apply only to the method calls that match the expected
argument list:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->with($arg1, $arg2, ...);
// or
$mock->shouldReceive('name_of_method')
->withArgs([$arg1, $arg2, ...]);
We can add a lot more flexibility to argument matching using the built in
matcher classes (see later). For example, ``\Mockery::any()`` matches any
argument passed to that position in the ``with()`` parameter list. Mockery also
allows Hamcrest library matchers - for example, the Hamcrest function
``anything()`` is equivalent to ``\Mockery::any()``.
It's important to note that this means all expectations attached only apply to
the given method when it is called with these exact arguments. This allows for
setting up differing expectations based on the arguments provided to expected
calls.
the given method when it is called with these exact arguments:
.. code-block:: php
withAnyArgs()
$mock = \Mockery::mock('MyClass');
Declares that this expectation matches a method call regardless of what
arguments are passed. This is set by default unless otherwise specified.
$mock->shouldReceive('foo')->with('Hello');
$mock->foo('Goodbye'); // throws a NoMatchingExpectationException
This allows for setting up differing expectations based on the arguments
provided to expected calls.
Argument matching with closures
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Instead of providing a built-in matcher for each argument, we can provide a
closure that matches all passed arguments at once:
.. code-block:: php
withNoArgs()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withArgs(closure);
Declares this expectation matches method calls with zero arguments.
The given closure receives all the arguments passed in the call to the expected
method. In this way, this expectation only applies to method calls where passed
arguments make the closure evaluate to true:
.. code-block:: php
andReturn(value)
$mock = \Mockery::mock('MyClass');
Sets a value to be returned from the expected method call.
$mock->shouldReceive('foo')->withArgs(function ($arg) {
if ($arg % 2 == 0) {
return true;
}
return false;
});
$mock->foo(4); // matches the expectation
$mock->foo(3); // throws a NoMatchingExpectationException
Argument matching with some of given values
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can provide expected arguments that match passed arguments when mocked method
is called.
.. code-block:: php
andReturn(value1, value2, ...)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withSomeOfArgs(arg1, arg2, arg3, ...);
Sets up a sequence of return values or closures. For example, the first call
will return value1 and the second value2. Note that all subsequent calls to a
mocked method will always return the final value (or the only value) given to
this declaration.
The given expected arguments order doesn't matter.
Check if expected values are included or not, but type should be matched:
.. code-block:: php
andReturnNull() / andReturn([NULL])
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->withSomeOfArgs(1, 2);
$mock->foo(1, 2, 3); // matches the expectation
$mock->foo(3, 2, 1); // matches the expectation (passed order doesn't matter)
$mock->foo('1', '2'); // throws a NoMatchingExpectationException (type should be matched)
$mock->foo(3); // throws a NoMatchingExpectationException
Any, or no arguments
^^^^^^^^^^^^^^^^^^^^
We can declare that the expectation matches a method call regardless of what
arguments are passed:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withAnyArgs();
This is set by default unless otherwise specified.
We can declare that the expectation matches method calls with zero arguments:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withNoArgs();
Declaring Return Value Expectations
-----------------------------------
For mock objects, we can tell Mockery what return values to return from the
expected method calls.
For that we can use the ``andReturn()`` method:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value);
This sets a value to be returned from the expected method call.
It is possible to set up expectation for multiple return values. By providing
a sequence of return values, we tell Mockery what value to return on every
subsequent call to the method:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value1, $value2, ...)
The first call will return ``$value1`` and the second call will return ``$value2``.
If we call the method more times than the number of return values we declared,
Mockery will return the final value for any subsequent method call:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->andReturn(1, 2, 3);
$mock->foo(); // int(1)
$mock->foo(); // int(2)
$mock->foo(); // int(3)
$mock->foo(); // int(3)
The same can be achieved using the alternative syntax:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnValues([$value1, $value2, ...])
It accepts a simple array instead of a list of parameters. The order of return
is determined by the numerical index of the given array with the last array
member being returned on all calls once previous return values are exhausted.
The following two options are primarily for communication with test readers:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnNull();
// or
$mock->shouldReceive('name_of_method')
->andReturn([null]);
Both of the above options are primarily for communication to test readers.
They mark the mock object method call as returning ``null`` or nothing.
.. code-block:: php
andReturnValues(array)
Alternative syntax for ``andReturn()`` that accepts a simple array instead of
a list of parameters. The order of return is determined by the numerical
index of the given array with the last array member being return on all calls
once previous return values are exhausted.
Sometimes we want to calculate the return results of the method calls, based on
the arguments passed to the method. We can do that with the ``andReturnUsing()``
method which accepts one or more closure:
.. code-block:: php
andReturnUsing(closure, ...)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnUsing(closure, ...);
Sets a closure (anonymous function) to be called with the arguments passed to
the method. The return value from the closure is then returned. Useful for
some dynamic processing of arguments into related concrete results. Closures
can queued by passing them as extra parameters as for ``andReturn()``.
Closures can be queued by passing them as extra parameters as for ``andReturn()``.
Occasionally, it can be useful to echo back one of the arguments that a method
is called with. In this case we can use the ``andReturnArg()`` method; the
argument to be returned is specified by its index in the arguments list:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnArg(1);
This returns the second argument (index #1) from the list of arguments when the
method is called.
.. note::
You cannot currently mix ``andReturnUsing()`` with ``andReturn()``.
We cannot currently mix ``andReturnUsing()`` or ``andReturnArg`` with
``andReturn()``.
If we are mocking fluid interfaces, the following method will be helpful:
.. code-block:: php
andThrow(Exception)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnSelf();
Declares that this method will throw the given ``Exception`` object when
called.
It sets the return value to the mocked class name.
Throwing Exceptions
-------------------
We can tell the method of mock objects to throw exceptions:
.. code-block:: php
andThrow(exception_name, message)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow(new Exception);
Rather than an object, you can pass in the ``Exception`` class and message to
use when throwing an ``Exception`` from the mocked method.
It will throw the given ``Exception`` object when called.
Rather than an object, we can pass in the ``Exception`` class, message and/or code to
use when throwing an ``Exception`` from the mocked method:
.. code-block:: php
andSet(name, value1) / set(name, value1)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow('exception_name', 'message', 123456789);
Used with an expectation so that when a matching method is called, one can
also cause a mock object's public property to be set to a specified value.
.. _expectations-setting-public-properties:
Setting Public Properties
-------------------------
Used with an expectation so that when a matching method is called, we can cause
a mock object's public property to be set to a specified value, by using
``andSet()`` or ``set()``:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andSet($property, $value);
// or
$mock->shouldReceive('name_of_method')
->set($property, $value);
In cases where we want to call the real method of the class that was mocked and
return its result, the ``passthru()`` method tells the expectation to bypass
a return queue:
.. code-block:: php
passthru()
Tells the expectation to bypass a return queue and instead call the real
method of the class that was mocked and return the result. Basically, it
allows expectation matching and call count validation to be applied against
It allows expectation matching and call count validation to be applied against
real methods while still calling the real class method with the expected
arguments.
.. code-block:: php
Declaring Call Count Expectations
---------------------------------
zeroOrMoreTimes()
Besides setting expectations on the arguments of the method calls, and the
return values of those same calls, we can set expectations on how many times
should any method be called.
Declares that the expected method may be called zero or more times. This is
the default for all methods unless otherwise set.
When a call count expectation is not met, a
``\Mockery\Expectation\InvalidCountException`` will be thrown.
.. note::
It is absolutely required to call ``\Mockery::close()`` at the end of our
tests, for example in the ``tearDown()`` method of PHPUnit. Otherwise
Mockery will not verify the calls made against our mock objects.
We can declare that the expected method may be called zero or more times:
.. code-block:: php
once()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->zeroOrMoreTimes();
Declares that the expected method may only be called once. Like all other call
count constraints, it will throw a ``\Mockery\CountValidator\Exception`` if
breached and can be modified by the ``atLeast()`` and ``atMost()``
constraints.
This is the default for all methods unless otherwise set.
To tell Mockery to expect an exact number of calls to a method, we can use the
following:
.. code-block:: php
twice()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->times($n);
Declares that the expected method may only be called twice.
where ``$n`` is the number of times the method should be called.
A couple of most common cases got their shorthand methods.
To declare that the expected method must be called one time only:
.. code-block:: php
times(n)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->once();
Declares that the expected method may only be called n times.
To declare that the expected method must be called two times:
.. code-block:: php
never()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->twice();
Declares that the expected method may never be called. Ever!
To declare that the expected method must never be called:
.. code-block:: php
atLeast()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->never();
Call count modifiers
^^^^^^^^^^^^^^^^^^^^
The call count expectations can have modifiers set.
If we want to tell Mockery the minimum number of times a method should be called,
we use ``atLeast()``:
.. code-block:: php
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atLeast()
->times(3);
Adds a minimum modifier to the next call count expectation. Thus
``atLeast()->times(3)`` means the call must be called at least three times
(given matching method args) but never less than three times.
Similarly, we can tell Mockery the maximum number of times a method should be
called, using ``atMost()``:
.. code-block:: php
atMost()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atMost()
->times(3);
Adds a maximum modifier to the next call count expectation. Thus
``atMost()->times(3)`` means the call must be called no more than three times.
This also means no calls are acceptable.
If the method gets no calls at all, the expectation will still be met.
We can also set a range of call counts, using ``between()``:
.. code-block:: php
between(min, max)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->between($min, $max);
Sets an expected range of call counts. This is actually identical to using
``atLeast()->times(min)->atMost()->times(max)`` but is provided as a
shorthand. It may be followed by a ``times()`` call with no parameter to
preserve the APIs natural language readability.
This is actually identical to using ``atLeast()->times($min)->atMost()->times($max)``
but is provided as a shorthand. It may be followed by a ``times()`` call with no
parameter to preserve the APIs natural language readability.
Multiple Calls with Different Expectations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If a method is expected to get called multiple times with different arguments
and/or return values we can simply repeat the expectations. The same of course
also works if we expect multiple calls to different methods.
.. code-block:: php
$mock = \Mockery::mock('MyClass');
// Expectations for the 1st call
$mock->shouldReceive('name_of_method');
->once()
->with('arg1')
->andReturn($value1)
// 2nd call to same method
->shouldReceive('name_of_method')
->once()
->with('arg2')
->andReturn($value2)
// final call to another method
->shouldReceive('other_method')
->once()
->with('other')
->andReturn($value_other);
Expectation Declaration Utilities
---------------------------------
Declares that this method is expected to be called in a specific order in
relation to similarly marked methods.
.. code-block:: php
ordered()
Declares that this method is expected to be called in a specific order in
relation to similarly marked methods. The order is dictated by the order in
which this modifier is actually used when setting up mocks.
The order is dictated by the order in which this modifier is actually used when
setting up mocks.
Declares the method as belonging to an order group (which can be named or
numbered). Methods within a group can be called in any order, but the ordered
calls from outside the group are ordered in relation to the group:
.. code-block:: php
ordered(group)
Declares the method as belonging to an order group (which can be named or
numbered). Methods within a group can be called in any order, but the ordered
calls from outside the group are ordered in relation to the group, i.e. you
can set up so that method1 is called before group1 which is in turn called
before method 2.
We can set up so that method1 is called before group1 which is in turn called
before method2.
When called prior to ``ordered()`` or ``ordered(group)``, it declares this
ordering to apply across all mock objects (not just the current mock):
.. code-block:: php
globally()
When called prior to ``ordered()`` or ``ordered(group)``, it declares this
ordering to apply across all mock objects (not just the current mock). This
allows for dictating order expectations across multiple mocks.
This allows for dictating order expectations across multiple mocks.
The ``byDefault()`` marks an expectation as a default. Default expectations are
applied unless a non-default expectation is created:
.. code-block:: php
byDefault()
Marks an expectation as a default. Default expectations are applied unless a
non-default expectation is created. These later expectations immediately
replace the previously defined default. This is useful so you can setup
default mocks in your unit test ``setup()`` and later tweak them in specific
tests as needed.
These later expectations immediately replace the previously defined default.
This is useful so we can setup default mocks in our unit test ``setup()`` and
later tweak them in specific tests as needed.
Returns the current mock object from an expectation chain:
.. code-block:: php
getMock()
Returns the current mock object from an expectation chain. Useful where you
prefer to keep mock setups as a single statement, e.g.
Useful where we prefer to keep mock setups as a single statement, e.g.:
.. code-block:: php

View File

@@ -7,17 +7,23 @@ Dealing with Final Classes/Methods
One of the primary restrictions of mock objects in PHP, is that mocking
classes or methods marked final is hard. The final keyword prevents methods so
marked from being replaced in subclasses (subclassing is how mock objects can
inherit the type of the class or object being mocked.
inherit the type of the class or object being mocked).
The simplest solution is not to mark classes or methods as final!
The simplest solution is to implement an interface in your final class and
typehint against / mock this.
However, in a compromise between mocking functionality and type safety,
However this may not be possible in some third party libraries.
Mockery does allow creating "proxy mocks" from classes marked final, or from
classes with methods marked final. This offers all the usual mock object
goodness but the resulting mock will not inherit the class type of the object
being mocked, i.e. it will not pass any instanceof comparison.
being mocked, i.e. it will not pass any instanceof comparison. Methods marked
as final will be proxied to the original method, i.e., final methods can't be
mocked.
You can create a proxy mock by passing the instantiated object you wish to
We can create a proxy mock by passing the instantiated object we wish to
mock into ``\Mockery::mock()``, i.e. Mockery will then generate a Proxy to the
real object and selectively intercept method calls for the purposes of setting
and meeting expectations.
See the :ref:`creating-test-doubles-partial-test-doubles` chapter, the subsection
about proxied partial test doubles.

View File

@@ -4,17 +4,19 @@ Reference
.. toctree::
:hidden:
startup_methods
creating_test_doubles
expectations
argument_validation
alternative_should_receive_syntax
spies
partial_mocks
protected_methods
public_properties
public_static_properties
pass_by_reference_behaviours
demeter_chains
object_recording
final_methods_classes
magic_methods
mockery/index
phpunit_integration
.. include:: map.rst.inc

View File

@@ -10,7 +10,7 @@ It is strongly recommended that unit tests and mock objects do not directly
refer to magic methods. Instead, refer only to the virtual methods and
properties these magic methods simulate.
Following this piece of advice will ensure you are testing the real API of
Following this piece of advice will ensure we are testing the real API of
classes and also ensures there is no conflict should Mockery override these
magic methods, which it will inevitably do in order to support its role in
intercepting method calls and properties.

View File

@@ -1,19 +1,14 @@
* :doc:`/reference/startup_methods`
* :doc:`/reference/creating_test_doubles`
* :doc:`/reference/expectations`
* :doc:`/reference/argument_validation`
* :doc:`/reference/alternative_should_receive_syntax`
* :doc:`/reference/spies`
* :doc:`/reference/partial_mocks`
* :doc:`/reference/protected_methods`
* :doc:`/reference/public_properties`
* :doc:`/reference/public_static_properties`
* :doc:`/reference/pass_by_reference_behaviours`
* :doc:`/reference/demeter_chains`
* :doc:`/reference/object_recording`
* :doc:`/reference/final_methods_classes`
* :doc:`/reference/magic_methods`
Mockery Reference
-----------------
* :doc:`/reference/mockery/configuration`
* :doc:`/reference/mockery/exceptions`
* :doc:`/reference/mockery/reserved_method_names`
* :doc:`/reference/mockery/gotchas`
* :doc:`/reference/phpunit_integration`

View File

@@ -1,52 +0,0 @@
.. index::
single: Mockery; Configuration
Mockery Global Configuration
============================
To allow for a degree of fine-tuning, Mockery utilises a singleton
configuration object to store a small subset of core behaviours. The three
currently present include:
* Option to allow/disallow the mocking of methods which do not actually exist
* Option to allow/disallow the existence of expectations which are never
fulfilled (i.e. unused)
* Setter/Getter for added a parameter map for internal PHP class methods
(``Reflection`` cannot detect these automatically)
By default, the first two behaviours are enabled. Of course, there are
situations where this can lead to unintended consequences. The mocking of
non-existent methods may allow mocks based on real classes/objects to fall out
of sync with the actual implementations, especially when some degree of
integration testing (testing of object wiring) is not being performed.
Allowing unfulfilled expectations means unnecessary mock expectations go
unnoticed, cluttering up test code, and potentially confusing test readers.
You may allow or disallow these behaviours (whether for whole test suites or
just select tests) by using one or both of the following two calls:
.. code-block:: php
\Mockery::getConfiguration()->allowMockingNonExistentMethods(bool);
\Mockery::getConfiguration()->allowMockingMethodsUnnecessarily(bool);
Passing a true allows the behaviour, false disallows it. Both take effect
immediately until switched back. In both cases, if either behaviour is
detected when not allowed, it will result in an Exception being thrown at that
point. Note that disallowing these behaviours should be carefully considered
since they necessarily remove at least some of Mockery's flexibility.
The other two methods are:
.. code-block:: php
\Mockery::getConfiguration()->setInternalClassMethodParamMap($class, $method, array $paramMap)
\Mockery::getConfiguration()->getInternalClassMethodParamMap($class, $method)
These are used to define parameters (i.e. the signature string of each) for the
methods of internal PHP classes (e.g. SPL, or PECL extension classes like
ext/mongo's MongoCollection. Reflection cannot analyse the parameters of internal
classes. Most of the time, you never need to do this. It's mainly needed where an
internal class method uses pass-by-reference for a parameter - you MUST in such
cases ensure the parameter signature includes the ``&`` symbol correctly as Mockery
won't correctly add it automatically for internal classes.

View File

@@ -1,93 +0,0 @@
.. index::
single: Mocking; Object Recording
Mock Object Recording
=====================
In certain cases, you may find that you are testing against an already
established pattern of behaviour, perhaps during refactoring. Rather then hand
crafting mock object expectations for this behaviour, you could instead use
the existing source code to record the interactions a real object undergoes
onto a mock object as expectations - expectations you can then verify against
an alternative or refactored version of the source code.
To record expectations, you need a concrete instance of the class to be
mocked. This can then be used to create a partial mock to which is given the
necessary code to execute the object interactions to be recorded. A simple
example is outline below (we use a closure for passing instructions to the
mock).
Here we have a very simple setup, a class (``SubjectUser``) which uses another
class (``Subject``) to retrieve some value. We want to record as expectations
on our mock (which will replace ``Subject`` later) all the calls and return
values of a Subject instance when interacting with ``SubjectUser``.
.. code-block:: php
class Subject
{
public function execute() {
return 'executed!';
}
}
class SubjectUser
{
public function use(Subject $subject) {
return $subject->execute();
}
}
Here's the test case showing the recording:
.. code-block:: php
class SubjectUserTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
\Mockery::close();
}
public function testSomething()
{
$mock = \Mockery::mock(new Subject);
$mock->shouldExpect(function ($subject) {
$user = new SubjectUser;
$user->use($subject);
});
/**
* Assume we have a replacement SubjectUser called NewSubjectUser.
* We want to verify it behaves identically to SubjectUser, i.e.
* it uses Subject in the exact same way
*/
$newSubject = new NewSubjectUser;
$newSubject->use($mock);
}
}
After the ``\Mockery::close()`` call in ``tearDown()`` validates the mock
object, we should have zero exceptions if ``NewSubjectUser`` acted on
`Subject` in a similar way to ``SubjectUser``. By default the order of calls
are not enforced, and loose argument matching is enabled, i.e. arguments may
be equal (``==``) but not necessarily identical (``===``).
If you wished to be more strict, for example ensuring the order of calls and
the final call counts were identical, or ensuring arguments are completely
identical, you can invoke the recorder's strict mode from the closure block,
e.g.
.. code-block:: php
$mock->shouldExpect(function ($subject) {
$subject->shouldBeStrict();
$user = new SubjectUser;
$user->use($subject);
});

View File

@@ -4,64 +4,78 @@
Creating Partial Mocks
======================
Partial mocks are useful when you only need to mock several methods of an
Partial mocks are useful when we only need to mock several methods of an
object leaving the remainder free to respond to calls normally (i.e. as
implemented). Mockery implements three distinct strategies for creating
partials. Each has specific advantages and disadvantages so which strategy you
use will depend on your own preferences and the source code in need of
partials. Each has specific advantages and disadvantages so which strategy we
use will depend on our own preferences and the source code in need of
mocking.
#. Traditional Partial Mock
#. Passive Partial Mock
We have previously talked a bit about :ref:`creating-test-doubles-partial-test-doubles`,
but we'd like to expand on the subject a bit here.
#. Runtime partial test doubles
#. Generated partial test doubles
#. Proxied Partial Mock
Traditional Partial Mock
------------------------
Runtime partial test doubles
----------------------------
A traditional partial mock, defines ahead of time which methods of a class are
to be mocked and which are to be left unmocked (i.e. callable as normal). The
syntax for creating traditional mocks is:
A runtime partial test double, also known as a passive partial mock, is a kind
of a default state of being for a mocked object.
.. code-block:: php
$mock = \Mockery::mock('MyClass')->makePartial();
With a runtime partial, we assume that all methods will simply defer to the
parent class (``MyClass``) original methods unless a method call matches a
known expectation. If we have no matching expectation for a specific method
call, that call is deferred to the class being mocked. Since the division
between mocked and unmocked calls depends entirely on the expectations we
define, there is no need to define which methods to mock in advance.
See the cookbook entry on :doc:`../cookbook/big_parent_class` for an example
usage of runtime partial test doubles.
Generated Partial Test Doubles
------------------------------
A generated partial test double, also known as a traditional partial mock,
defines ahead of time which methods of a class are to be mocked and which are
to be left unmocked (i.e. callable as normal). The syntax for creating
traditional mocks is:
.. code-block:: php
$mock = \Mockery::mock('MyClass[foo,bar]');
In the above example, the ``foo()`` and ``bar()`` methods of MyClass will be
mocked but no other MyClass methods are touched. You will need to define
mocked but no other MyClass methods are touched. We will need to define
expectations for the ``foo()`` and ``bar()`` methods to dictate their mocked
behaviour.
Don't forget that you can pass in constructor arguments since unmocked methods
Don't forget that we can pass in constructor arguments since unmocked methods
may rely on those!
.. code-block:: php
$mock = \Mockery::mock('MyNamespace\MyClass[foo]', array($arg1, $arg2));
Passive Partial Mock
--------------------
See the :ref:`creating-test-doubles-constructor-arguments` section to read up
on them.
A passive partial mock is more of a default state of being.
.. note::
.. code-block:: php
$mock = \Mockery::mock('MyClass')->makePartial();
In a passive partial, we assume that all methods will simply defer to the
parent class (``MyClass``) original methods unless a method call matches a
known expectation. If you have no matching expectation for a specific method
call, that call is deferred to the class being mocked. Since the division
between mocked and unmocked calls depends entirely on the expectations you
define, there is no need to define which methods to mock in advance. The
``makePartial()`` method is identical to the original ``shouldDeferMissing()``
method which first introduced this Partial Mock type.
Even though we support generated partial test doubles, we do not recommend
using them.
Proxied Partial Mock
--------------------
A proxied partial mock is a partial of last resort. You may encounter a class
A proxied partial mock is a partial of last resort. We may encounter a class
which is simply not capable of being mocked because it has been marked as
final. Similarly, you may find a class with methods marked as final. In such a
final. Similarly, we may find a class with methods marked as final. In such a
scenario, we cannot simply extend the class and override methods to mock - we
need to get creative.
@@ -70,8 +84,8 @@ need to get creative.
$mock = \Mockery::mock(new MyClass);
Yes, the new mock is a Proxy. It intercepts calls and reroutes them to the
proxied object (which you construct and pass in) for methods which are not
subject to any expectations. Indirectly, this allows you to mock methods
proxied object (which we construct and pass in) for methods which are not
subject to any expectations. Indirectly, this allows us to mock methods
marked final since the Proxy is not subject to those limitations. The tradeoff
should be obvious - a proxied partial will fail any typehint checks for the
class being mocked since it cannot extend that class.
@@ -79,15 +93,15 @@ class being mocked since it cannot extend that class.
Special Internal Cases
----------------------
All mock objects, with the exception of Proxied Partials, allow you to make
any expectation call the underlying real class method using the ``passthru()``
All mock objects, with the exception of Proxied Partials, allows us to make
any expectation call to the underlying real class method using the ``passthru()``
expectation call. This will return values from the real call and bypass any
mocked return queue (which can simply be omitted obviously).
There is a fourth kind of partial mock reserved for internal use. This is
automatically generated when you attempt to mock a class containing methods
automatically generated when we attempt to mock a class containing methods
marked final. Since we cannot override such methods, they are simply left
unmocked. Typically, you don't need to worry about this but if you really
unmocked. Typically, we don't need to worry about this but if we really
really must mock a final method, the only possible means is through a Proxied
Partial Mock. SplFileInfo, for example, is a common class subject to this form
of automatic internal partial since it contains public final methods used

View File

@@ -6,7 +6,7 @@ Preserving Pass-By-Reference Method Parameter Behaviour
PHP Class method may accept parameters by reference. In this case, changes
made to the parameter (a reference to the original variable passed to the
method) are reflected in the original variable. A simple example:
method) are reflected in the original variable. An example:
.. code-block:: php
@@ -26,21 +26,21 @@ method) are reflected in the original variable. A simple example:
echo $baz; // will echo the integer 2
In the example above, the variable $baz is passed by reference to
In the example above, the variable ``$baz`` is passed by reference to
``Foo::bar()`` (notice the ``&`` symbol in front of the parameter?). Any
change ``bar()`` makes to the parameter reference is reflected in the original
variable, ``$baz``.
Mockery 0.7+ handles references correctly for all methods where it can analyse
Mockery handles references correctly for all methods where it can analyse
the parameter (using ``Reflection``) to see if it is passed by reference. To
mock how a reference is manipulated by the class method, you can use a closure
mock how a reference is manipulated by the class method, we can use a closure
argument matcher to manipulate it, i.e. ``\Mockery::on()`` - see the
":doc:`argument_validation`" chapter.
:ref:`argument-validation-complex-argument-validation` chapter.
There is an exception for internal PHP classes where Mockery cannot analyse
method parameters using ``Reflection`` (a limitation in PHP). To work around
this, you can explicitly declare method parameters for an internal class using
``/Mockery/Configuration::setInternalClassMethodParamMap()``.
this, we can explicitly declare method parameters for an internal class using
``\Mockery\Configuration::setInternalClassMethodParamMap()``.
Here's an example using ``MongoCollection::insert()``. ``MongoCollection`` is
an internal class offered by the mongo extension from PECL. Its ``insert()``
@@ -81,3 +81,50 @@ preserved:
\Mockery::resetContainer();
}
Protected Methods
-----------------
When dealing with protected methods, and trying to preserve pass by reference
behavior for them, a different approach is required.
.. code-block:: php
class Model
{
public function test(&$data)
{
return $this->doTest($data);
}
protected function doTest(&$data)
{
$data['something'] = 'wrong';
return $this;
}
}
class Test extends \PHPUnit\Framework\TestCase
{
public function testModel()
{
$mock = \Mockery::mock('Model[test]')->shouldAllowMockingProtectedMethods();
$mock->shouldReceive('test')
->with(\Mockery::on(function(&$data) {
$data['something'] = 'wrong';
return true;
}));
$data = array('foo' => 'bar');
$mock->test($data);
$this->assertTrue(isset($data['something']));
$this->assertEquals('wrong', $data['something']);
}
}
This is quite an edge case, so we need to change the original code a little bit,
by creating a public method that will call our protected method, and then mock
that, instead of the protected method. This new public method will act as a
proxy to our protected method.

View File

@@ -6,8 +6,8 @@ PHPUnit Integration
Mockery was designed as a simple-to-use *standalone* mock object framework, so
its need for integration with any testing framework is entirely optional. To
integrate Mockery, you just need to define a ``tearDown()`` method for your
tests containing the following (you may use a shorter ``\Mockery`` namespace
integrate Mockery, we need to define a ``tearDown()`` method for our tests
containing the following (we may use a shorter ``\Mockery`` namespace
alias):
.. code-block:: php
@@ -17,16 +17,16 @@ alias):
}
This static call cleans up the Mockery container used by the current test, and
run any verification tasks needed for your expectations.
run any verification tasks needed for our expectations.
For some added brevity when it comes to using Mockery, you can also explicitly
For some added brevity when it comes to using Mockery, we can also explicitly
use the Mockery namespace with a shorter alias. For example:
.. code-block:: php
use \Mockery as m;
class SimpleTest extends PHPUnit_Framework_TestCase
class SimpleTest extends \PHPUnit\Framework\TestCase
{
public function testSimpleMock() {
$mock = m::mock('simplemock');
@@ -40,9 +40,9 @@ use the Mockery namespace with a shorter alias. For example:
}
}
Mockery ships with an autoloader so you don't need to litter your tests with
``require_once()`` calls. To use it, ensure Mockery is on your
``include_path`` and add the following to your test suite's ``Bootstrap.php``
Mockery ships with an autoloader so we don't need to litter our tests with
``require_once()`` calls. To use it, ensure Mockery is on our
``include_path`` and add the following to our test suite's ``Bootstrap.php``
or ``TestHelper.php`` file:
.. code-block:: php
@@ -53,8 +53,8 @@ or ``TestHelper.php`` file:
$loader = new \Mockery\Loader;
$loader->register();
If you are using Composer, you can simplify this to just including the
Composer generated autoloader file:
If we are using Composer, we can simplify this to including the Composer
generated autoloader file:
.. code-block:: php
@@ -67,22 +67,41 @@ Composer generated autoloader file:
the file name is updated for all your projects.)
To integrate Mockery into PHPUnit and avoid having to call the close method
and have Mockery remove itself from code coverage reports, use this in you
suite:
and have Mockery remove itself from code coverage reports, have your test case
extends the ``\Mockery\Adapter\Phpunit\MockeryTestCase``:
.. code-block:: php
// Create Suite
$suite = new PHPUnit_Framework_TestSuite();
class MyTest extends \Mockery\Adapter\Phpunit\MockeryTestCase
{
// Create a result listener or add it
$result = new PHPUnit_Framework_TestResult();
$result->addListener(new \Mockery\Adapter\Phpunit\TestListener());
}
// Run the tests.
$suite->run($result);
An alternative is to use the supplied trait:
If you are using PHPUnit's XML configuration approach, you can include the
.. code-block:: php
class MyTest extends \PHPUnit\Framework\TestCase
{
use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
}
Extending ``MockeryTestCase`` or using the ``MockeryPHPUnitIntegration``
trait is **the recommended way** of integrating Mockery with PHPUnit,
since Mockery 1.0.0.
PHPUnit listener
----------------
Before the 1.0.0 release, Mockery provided a PHPUnit listener that would
call ``Mockery::close()`` for us at the end of a test. This has changed
significantly since the 1.0.0 version.
Now, Mockery provides a PHPUnit listener that makes tests fail if
``Mockery::close()`` has not been called. It can help identify tests where
we've forgotten to include the trait or extend the ``MockeryTestCase``.
If we are using PHPUnit's XML configuration approach, we can include the
following to load the ``TestListener``:
.. code-block:: xml
@@ -92,19 +111,35 @@ following to load the ``TestListener``:
</listeners>
Make sure Composer's or Mockery's autoloader is present in the bootstrap file
or you will need to also define a "file" attribute pointing to the file of the
above ``TestListener`` class.
or we will need to also define a "file" attribute pointing to the file of the
``TestListener`` class.
If we are creating the test suite programmatically we may add the listener
like this:
.. code-block:: php
// Create the suite.
$suite = new PHPUnit\Framework\TestSuite();
// Create the listener and add it to the suite.
$result = new PHPUnit\Framework\TestResult();
$result->addListener(new \Mockery\Adapter\Phpunit\TestListener());
// Run the tests.
$suite->run($result);
.. caution::
PHPUnit provides a functionality that allows
`tests to run in a separated process <http://phpunit.de/manual/4.0/en/appendixes.annotations.html#appendixes.annotations.runTestsInSeparateProcesses>`_,
`tests to run in a separated process <http://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.runTestsInSeparateProcesses>`_,
to ensure better isolation. Mockery verifies the mocks expectations using the
``Mockery::close()`` method, and provides a PHPUnit listener, that automatically
calls this method for you after every test.
calls this method for us after every test.
However, this listener is not called in the right process when using PHPUnit's process
isolation, resulting in expectations that might not be respected, but without raising
any ``Mockery\Exception``. To avoid this, you cannot rely on the supplied Mockery PHPUnit
``TestListener``, and you need to explicitly calls ``Mockery::close``. The easiest solution
to include this call in the ``tearDown()`` method, as explained previously.
However, this listener is not called in the right process when using
PHPUnit's process isolation, resulting in expectations that might not be
respected, but without raising any ``Mockery\Exception``. To avoid this,
we cannot rely on the supplied Mockery PHPUnit ``TestListener``, and we need
to explicitly call ``Mockery::close``. The easiest solution to include this
call in the ``tearDown()`` method, as explained previously.

View File

@@ -0,0 +1,26 @@
.. index::
single: Mocking; Protected Methods
Mocking Protected Methods
=========================
By default, Mockery does not allow mocking protected methods. We do not recommend
mocking protected methods, but there are cases when there is no other solution.
For those cases we have the ``shouldAllowMockingProtectedMethods()`` method. It
instructs Mockery to specifically allow mocking of protected methods, for that
one class only:
.. code-block:: php
class MyClass
{
protected function foo()
{
}
}
$mock = \Mockery::mock('MyClass')
->shouldAllowMockingProtectedMethods();
$mock->shouldReceive('foo');

View File

@@ -4,13 +4,17 @@
Mocking Public Properties
=========================
Mockery allows you to mock properties in several ways. The simplest is that
you can simply set a public property and value on any mock object. The second
is that you can use the expectation methods ``set()`` and ``andSet()`` to set
property values if that expectation is ever met.
Mockery allows us to mock properties in several ways. One way is that we can set
a public property and its value on any mock object. The second is that we can
use the expectation methods ``set()`` and ``andSet()`` to set property values if
that expectation is ever met.
You should note that, in general, Mockery does not support mocking any magic
methods since these are generally not considered a public API (and besides
they are a PITA to differentiate when you badly need them for mocking!). So
please mock virtual properties (those relying on ``__get()`` and ``__set()``)
as if they were actually declared on the class.
You can read more about :ref:`expectations-setting-public-properties`.
.. note::
In general, Mockery does not support mocking any magic methods since these
are generally not considered a public API (and besides it is a bit difficult
to differentiate them when you badly need them for mocking!). So please mock
virtual properties (those relying on ``__get()`` and ``__set()``) as if they
were actually declared on the class.

View File

@@ -10,3 +10,6 @@ name which would normally be loaded (via autoloading or a require statement)
in the system under test. These aliases block that loading (unless via a
require statement - so please use autoloading!) and allow Mockery to intercept
static method calls and add expectations for them.
See the :ref:`creating-test-doubles-aliasing` section for more information on
creating aliased mocks, for the purpose of mocking public static methods.

View File

@@ -1,130 +0,0 @@
.. index::
single: Reference; Examples
Quick Examples
==============
Create a mock object to return a sequence of values from a set of method
calls.
.. code-block:: php
class SimpleTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
\Mockery::close();
}
public function testSimpleMock()
{
$mock = \Mockery::mock(array('pi' => 3.1416, 'e' => 2.71));
$this->assertEquals(3.1416, $mock->pi());
$this->assertEquals(2.71, $mock->e());
}
}
Create a mock object which returns a self-chaining Undefined object for a
method call.
.. code-block:: php
use \Mockery as m;
class UndefinedTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testUndefinedValues()
{
$mock = m::mock('mymock');
$mock->shouldReceive('divideBy')->with(0)->andReturnUndefined();
$this->assertTrue($mock->divideBy(0) instanceof \Mockery\Undefined);
}
}
Creates a mock object which multiple query calls and a single update call.
.. code-block:: php
use \Mockery as m;
class DbTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testDbAdapter()
{
$mock = m::mock('db');
$mock->shouldReceive('query')->andReturn(1, 2, 3);
$mock->shouldReceive('update')->with(5)->andReturn(NULL)->once();
// ... test code here using the mock
}
}
Expect all queries to be executed before any updates.
.. code-block:: php
use \Mockery as m;
class DbTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testQueryAndUpdateOrder()
{
$mock = m::mock('db');
$mock->shouldReceive('query')->andReturn(1, 2, 3)->ordered();
$mock->shouldReceive('update')->andReturn(NULL)->once()->ordered();
// ... test code here using the mock
}
}
Create a mock object where all queries occur after startup, but before finish,
and where queries are expected with several different params.
.. code-block:: php
use \Mockery as m;
class DbTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testOrderedQueries()
{
$db = m::mock('db');
$db->shouldReceive('startup')->once()->ordered();
$db->shouldReceive('query')->with('CPWR')->andReturn(12.3)->once()->ordered('queries');
$db->shouldReceive('query')->with('MSFT')->andReturn(10.0)->once()->ordered('queries');
$db->shouldReceive('query')->with("/^....$/")->andReturn(3.3)->atLeast()->once()->ordered('queries');
$db->shouldReceive('finish')->once()->ordered();
// ... test code here using the mock
}
}

View File

@@ -0,0 +1,154 @@
.. index::
single: Reference; Spies
Spies
=====
Spies are a type of test doubles, but they differ from stubs or mocks in that,
that the spies record any interaction between the spy and the System Under Test
(SUT), and allow us to make assertions against those interactions after the fact.
Creating a spy means we don't have to set up expectations for every method call
the double might receive during the test, some of which may not be relevant to
the current test. A spy allows us to make assertions about the calls we care
about for this test only, reducing the chances of over-specification and making
our tests more clear.
Spies also allow us to follow the more familiar Arrange-Act-Assert or
Given-When-Then style within our tests. With mocks, we have to follow a less
familiar style, something along the lines of Arrange-Expect-Act-Assert, where
we have to tell our mocks what to expect before we act on the SUT, then assert
that those expectations where met:
.. code-block:: php
// arrange
$mock = \Mockery::mock('MyDependency');
$sut = new MyClass($mock);
// expect
$mock->shouldReceive('foo')
->once()
->with('bar');
// act
$sut->callFoo();
// assert
\Mockery::close();
Spies allow us to skip the expect part and move the assertion to after we have
acted on the SUT, usually making our tests more readable:
.. code-block:: php
// arrange
$spy = \Mockery::spy('MyDependency');
$sut = new MyClass($spy);
// act
$sut->callFoo();
// assert
$spy->shouldHaveReceived()
->foo()
->with('bar');
On the other hand, spies are far less restrictive than mocks, meaning tests are
usually less precise, as they let us get away with more. This is usually a
good thing, they should only be as precise as they need to be, but while spies
make our tests more intent-revealing, they do tend to reveal less about the
design of the SUT. If we're having to setup lots of expectations for a mock,
in lots of different tests, our tests are trying to tell us something - the SUT
is doing too much and probably should be refactored. We don't get this with
spies, they simply ignore the calls that aren't relevant to them.
Another downside to using spies is debugging. When a mock receives a call that
it wasn't expecting, it immediately throws an exception (failing fast), giving
us a nice stack trace or possibly even invoking our debugger. With spies, we're
simply asserting calls were made after the fact, so if the wrong calls were made,
we don't have quite the same just in time context we have with the mocks.
Finally, if we need to define a return value for our test double, we can't do
that with a spy, only with a mock object.
.. note::
This documentation page is an adaption of the blog post titled
`"Mockery Spies" <https://davedevelopment.co.uk/2014/10/09/mockery-spies.html>`_,
published by Dave Marshall on his blog. Dave is the original author of spies
in Mockery.
Spies Reference
---------------
To verify that a method was called on a spy, we use the ``shouldHaveReceived()``
method:
.. code-block:: php
$spy->shouldHaveReceived('foo');
To verify that a method was **not** called on a spy, we use the
``shouldNotHaveReceived()`` method:
.. code-block:: php
$spy->shouldNotHaveReceived('foo');
We can also do argument matching with spies:
.. code-block:: php
$spy->shouldHaveReceived('foo')
->with('bar');
Argument matching is also possible by passing in an array of arguments to
match:
.. code-block:: php
$spy->shouldHaveReceived('foo', ['bar']);
Although when verifying a method was not called, the argument matching can only
be done by supplying the array of arguments as the 2nd argument to the
``shouldNotHaveReceived()`` method:
.. code-block:: php
$spy->shouldNotHaveReceived('foo', ['bar']);
This is due to Mockery's internals.
Finally, when expecting calls that should have been received, we can also verify
the number of calls:
.. code-block:: php
$spy->shouldHaveReceived('foo')
->with('bar')
->twice();
Alternative shouldReceive syntax
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As of Mockery 1.0.0, we support calling methods as we would call any PHP method,
and not as string arguments to Mockery ``should*`` methods.
In cases of spies, this only applies to the ``shouldHaveReceived()`` method:
.. code-block:: php
$spy->shouldHaveReceived()
->foo('bar');
We can set expectation on number of calls as well:
.. code-block:: php
$spy->shouldHaveReceived()
->foo('bar')
->twice();
Unfortunately, due to limitations we can't support the same syntax for the
``shouldNotHaveReceived()`` method.

View File

@@ -1,230 +0,0 @@
.. index::
single: Reference; Quick Reference
Quick Reference
===============
Mockery implements a shorthand API when creating a mock. Here's a sampling of
the possible startup methods.
.. code-block:: php
$mock = \Mockery::mock('foo');
Creates a mock object named "foo". In this case, "foo" is a name (not
necessarily a class name) used as a simple identifier when raising exceptions.
This creates a mock object of type ``\Mockery\Mock`` and is the loosest form
of mock possible.
.. code-block:: php
$mock = \Mockery::mock(array('foo'=>1,'bar'=>2));
Creates an mock object named unknown since we passed no name. However we did
pass an expectation array, a quick method of setting up methods to expect with
their return values.
.. code-block:: php
$mock = \Mockery::mock('foo', array('foo'=>1,'bar'=>2));
Similar to the previous examples and all examples going forward, expectation
arrays can be passed for all mock objects as the second parameter to
``mock()``.
.. code-block:: php
$mock = \Mockery::mock('foo', function($mock) {
$mock->shouldReceive(method_name);
});
In addition to expectation arrays, you can also pass in a closure which
contains reusable expectations. This can be passed as the second parameter, or
as the third parameter if partnered with an expectation array. This is one
method for creating reusable mock expectations.
.. code-block:: php
$mock = \Mockery::mock('stdClass');
Creates a mock identical to a named mock, except the name is an actual class
name. Creates a simple mock as previous examples show, except the mock object
will inherit the class type (via inheritance), i.e. it will pass type hints or
instanceof evaluations for stdClass. Useful where a mock object must be of a
specific type.
.. code-block:: php
$mock = \Mockery::mock('FooInterface');
You can create mock objects based on any concrete class, abstract class or
even an interface. Again, the primary purpose is to ensure the mock object
inherits a specific type for type hinting. There is an exception in that
classes marked final, or with methods marked final, cannot be mocked fully. In
these cases a partial mock (explained later) must be utilised.
.. code-block:: php
$mock = \Mockery::mock('alias:MyNamespace\MyClass');
Prefixing the valid name of a class (which is NOT currently loaded) with
"alias:" will generate an "alias mock". Alias mocks create a class alias with
the given classname to stdClass and are generally used to enable the mocking
of public static methods. Expectations set on the new mock object which refer
to static methods will be used by all static calls to this class.
.. code-block:: php
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
Prefixing the valid name of a class (which is NOT currently loaded) with
"overload:" will generate an alias mock (as with "alias:") except that created
new instances of that class will import any expectations set on the origin
mock (``$mock``). The origin mock is never verified since it's used an
expectation store for new instances. For this purpose we use the term
"instance mock" to differentiate it from the simpler "alias mock".
.. note::
Using alias/instance mocks across more than one test will generate a fatal
error since you can't have two classes of the same name. To avoid this,
run each test of this kind in a separate PHP process (which is supported
out of the box by both PHPUnit and PHPT).
.. code-block:: php
$mock = \Mockery::mock('stdClass, MyInterface1, MyInterface2');
The first argument can also accept a list of interfaces that the mock object
must implement, optionally including no more than one existing class to be
based on. The class name doesn't need to be the first member of the list but
it's a friendly convention to use for readability. All subsequent arguments
remain unchanged from previous examples.
If the given class does not exist, you must define and include it beforehand
or a ``\Mockery\Exception`` will be thrown.
.. code-block:: php
$mock = \Mockery::mock('MyNamespace\MyClass[foo,bar]');
The syntax above tells Mockery to partially mock the ``MyNamespace\MyClass``
class, by mocking the ``foo()`` and ``bar()`` methods only. Any other method
will be not be overridden by Mockery. This traditional form of "partial mock"
can be applied to any class or abstract class (e.g. mocking abstract methods
where a concrete implementation does not exist yet). If you attempt to partial
mock a method marked final, it will actually be ignored in that instance
leaving the final method untouched. This is necessary since mocking of final
methods is, by definition in PHP, impossible.
Please refer to ":doc:`partial_mocks`" for a detailed explanation on how to
create Partial Mocks in Mockery.
.. code-block:: php
$mock = \Mockery::mock("MyNamespace\MyClass[foo]", array($arg1, $arg2));
If Mockery encounters an indexed array as the second or third argument, it
will assume they are constructor parameters and pass them when constructing
the mock object. The syntax above will create a new partial mock, particularly
useful if method ``bar`` calls method ``foo`` internally with
``$this->foo()``.
.. code-block:: php
$mock = \Mockery::mock(new Foo);
Passing any real object into Mockery will create a Proxied Partial Mock. This
can be useful if real partials are impossible, e.g. a final class or class
where you absolutely must override a method marked final. Since you can
already create a concrete object, so all we need to do is selectively override
a subset of existing methods (or add non-existing methods!) for our
expectations.
A little revision: All mock methods accept the class, object or alias name to
be mocked as the first parameter. The second parameter can be an expectation
array of methods and their return values, or an expectation closure (which can
be the third param if used in conjunction with an expectation array).
.. code-block:: php
\Mockery::self()
At times, you will discover that expectations on a mock include methods which
need to return the same mock object (e.g. a common case when designing a
Domain Specific Language (DSL) such as the one Mockery itself uses!). To
facilitate this, calling ``\Mockery::self()`` will always return the last Mock
Object created by calling ``\Mockery::mock()``. For example:
.. code-block:: php
$mock = \Mockery::mock('BazIterator')
->shouldReceive('next')
->andReturn(\Mockery::self())
->mock();
The above class being mocked, as the ``next()`` method suggests, is an
iterator. In many cases, you can replace all the iterated elements (since they
are the same type many times) with just the one mock object which is
programmed to act as discrete iterated elements.
.. code-block:: php
$mock = \Mockery::namedMock('MyClassName', 'DateTime');
The ``namedMock`` method will generate a class called by the first argument,
so in this example ``MyClassName``. The rest of the arguments are treat in the
same way as the ``mock`` method, so again, this example would create a class
called ``MyClassName`` that extends ``DateTime``.
Named mocks are quite an edge case, but they can be useful when code depends
on the ``__CLASS__`` magic constant, or when you need two derivatives of an
abstract type, that are actually different classes.
.. caution::
You can only create a named mock once, any subsequent calls to
``namedMock``, with different arguments are likely to cause exceptions.
Behaviour Modifiers
-------------------
When creating a mock object, you may wish to use some commonly preferred
behaviours that are not the default in Mockery.
.. code-block:: php
\Mockery::mock('MyClass')->shouldIgnoreMissing()
The use of the ``shouldIgnoreMissing()`` behaviour modifier will label this
mock object as a Passive Mock. In such a mock object, calls to methods which
are not covered by expectations will return ``null`` instead of the usual
complaining about there being no expectation matching the call.
You can optionally prefer to return an object of type ``\Mockery\Undefined``
(i.e. a ``null`` object) (which was the 0.7.2 behaviour) by using an
additional modifier:
.. code-block:: php
\Mockery::mock('MyClass')->shouldIgnoreMissing()->asUndefined()
The returned object is nothing more than a placeholder so if, by some act of
fate, it's erroneously used somewhere it shouldn't it will likely not pass a
logic check.
.. code-block:: php
\Mockery::mock('MyClass')->makePartial()
also
.. code-block:: php
\Mockery::mock('MyClass')->shouldDeferMissing()
Known as a Passive Partial Mock (not to be confused with real partial mock
objects discussed later), this form of mock object will defer all methods not
subject to an expectation to the parent class of the mock, i.e. ``MyClass``.
Whereas the previous ``shouldIgnoreMissing()`` returned ``null``, this
behaviour simply calls the parent's matching method.

View File

@@ -1,11 +0,0 @@
<?php
set_include_path(
dirname(__FILE__) . '/../../library'
. PATH_SEPARATOR . get_include_path()
);
require_once 'Mockery/Loader.php';
$loader = new \Mockery\Loader;
$loader->register();

View File

@@ -1,24 +0,0 @@
<?php
class Starship
{
protected $_engineering = null;
public function __construct($engineering)
{
$this->_engineering = $engineering;
}
public function enterOrbit()
{
$this->_engineering->disengageWarp();
$this->_engineering->runDiagnosticLevel(5);
$this->_engineering->divertPower(0.40, 'sensors');
$this->_engineering->divertPower(0.30, 'auxengines');
$this->_engineering->runDiagnosticLevel(1);
// We can add more runDiagnosticLevel() calls without failing the test
// anywhere above since they are unordered.
}
}

View File

@@ -1,21 +0,0 @@
<?php
use \Mockery as M;
require_once 'Starship.php';
class StarshipTest extends PHPUnit_Framework_TestCase
{
public function testEngineeringResponseToEnteringOrbit()
{
$mock = M::mock('Engineering');
$mock->shouldReceive('disengageWarp')->once()->ordered();
$mock->shouldReceive('divertPower')->with(0.40, 'sensors')->once()->ordered();
$mock->shouldReceive('divertPower')->with(0.30, 'auxengines')->once()->ordered();
$mock->shouldReceive('runDiagnosticLevel')->with(1)->once()->ordered();
$mock->shouldReceive('runDiagnosticLevel')->with(M::type('int'))->zeroOrMoreTimes();
$starship = new Starship($mock);
$starship->enterOrbit();
}
}

View File

@@ -1,10 +0,0 @@
<phpunit bootstrap="./Bootstrap.php">
<testsuite name="Starship Test Suite">
<directory>./</directory>
</testsuite>
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener"
file="Mockery/Adapter/Phpunit/TestListener.php">
</listener>
</listeners>
</phpunit>

View File

@@ -14,25 +14,21 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
use Mockery\ClosureWrapper;
use Mockery\ExpectationInterface;
use Mockery\Generator\CachingGenerator;
use Mockery\Generator\Generator;
use Mockery\Generator\MockConfigurationBuilder;
use Mockery\Generator\MockNameBuilder;
use Mockery\Generator\StringManipulationGenerator;
use Mockery\Generator\StringManipulation\Pass\CallTypeHintPass;
use Mockery\Generator\StringManipulation\Pass\ClassNamePass;
use Mockery\Generator\StringManipulation\Pass\ClassPass;
use Mockery\Generator\StringManipulation\Pass\InstanceMockPass;
use Mockery\Generator\StringManipulation\Pass\InterfacePass;
use Mockery\Generator\StringManipulation\Pass\MethodDefinitionPass;
use Mockery\Generator\StringManipulation\Pass\RemoveBuiltinMethodsThatAreFinalPass;
use Mockery\Generator\StringManipulation\Pass\RemoveUnserializeForInternalSerializableClassesPass;
use Mockery\Loader\EvalLoader;
use Mockery\Loader\Loader;
use Mockery\Matcher\MatcherAbstract;
use Mockery\Reflector;
class Mockery
{
@@ -41,7 +37,7 @@ class Mockery
/**
* Global container to hold all mocks for the current unit test running.
*
* @var \Mockery\Container
* @var \Mockery\Container|null
*/
protected static $_container = null;
@@ -65,47 +61,100 @@ class Mockery
/**
* @var array
*/
private static $_filesToCleanUp = array();
private static $_filesToCleanUp = [];
/**
* Defines the global helper functions
*
* @return void
*/
public static function globalHelpers()
{
require_once __DIR__ . '/helpers.php';
}
/**
* @return array
*
* @deprecated since 1.3.2 and will be removed in 2.0.
*/
public static function builtInTypes()
{
return array(
'array',
'bool',
'callable',
'float',
'int',
'iterable',
'object',
'self',
'string',
'void',
);
}
/**
* @param string $type
* @return bool
*
* @deprecated since 1.3.2 and will be removed in 2.0.
*/
public static function isBuiltInType($type)
{
return in_array($type, \Mockery::builtInTypes());
}
/**
* Static shortcut to \Mockery\Container::mock().
*
* @return \Mockery\MockInterface
* @param mixed ...$args
*
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public static function mock()
public static function mock(...$args)
{
$args = func_get_args();
return call_user_func_array(array(self::getContainer(), 'mock'), $args);
}
/**
* @return \Mockery\MockInterface
* Static and semantic shortcut for getting a mock from the container
* and applying the spy's expected behavior into it.
*
* @param mixed ...$args
*
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public static function spy()
public static function spy(...$args)
{
$args = func_get_args();
if (count($args) && $args[0] instanceof \Closure) {
$args[0] = new ClosureWrapper($args[0]);
}
return call_user_func_array(array(self::getContainer(), 'mock'), $args)->shouldIgnoreMissing();
}
/**
* @return \Mockery\MockInterface
* Static and Semantic shortcut to \Mockery\Container::mock().
*
* @param mixed ...$args
*
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public static function instanceMock()
public static function instanceMock(...$args)
{
$args = func_get_args();
return call_user_func_array(array(self::getContainer(), 'mock'), $args);
}
/**
* Static shortcut to \Mockery\Container::mock(), first argument names the mock.
*
* @return \Mockery\MockInterface
* @param mixed ...$args
*
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public static function namedMock()
public static function namedMock(...$args)
{
$args = func_get_args();
$name = array_shift($args);
$builder = new MockConfigurationBuilder();
@@ -121,7 +170,7 @@ class Mockery
*
* @throws LogicException
*
* @return \Mockery\MockInterface
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public static function self()
{
@@ -143,31 +192,36 @@ class Mockery
foreach (self::$_filesToCleanUp as $fileName) {
@unlink($fileName);
}
self::$_filesToCleanUp = array();
self::$_filesToCleanUp = [];
if (is_null(self::$_container)) {
return;
}
self::$_container->mockery_teardown();
self::$_container->mockery_close();
$container = self::$_container;
self::$_container = null;
$container->mockery_teardown();
$container->mockery_close();
}
/**
* Static fetching of a mock associated with a name or explicit class poser.
*
* @param $name
* @param string $name
*
* @return \Mockery\Mock
*/
public static function fetchMock($name)
{
return self::$_container->fetchMock($name);
return self::getContainer()->fetchMock($name);
}
/**
* Get the container.
* Lazy loader and getter for
* the container property.
*
* @return Mockery\Container
*/
public static function getContainer()
{
@@ -179,6 +233,8 @@ class Mockery
}
/**
* Setter for the $_generator static property.
*
* @param \Mockery\Generator\Generator $generator
*/
public static function setGenerator(Generator $generator)
@@ -186,6 +242,12 @@ class Mockery
self::$_generator = $generator;
}
/**
* Lazy loader method and getter for
* the generator property.
*
* @return Generator
*/
public static function getGenerator()
{
if (is_null(self::$_generator)) {
@@ -195,23 +257,20 @@ class Mockery
return self::$_generator;
}
/**
* Creates and returns a default generator
* used inside this class.
*
* @return CachingGenerator
*/
public static function getDefaultGenerator()
{
$generator = new StringManipulationGenerator(array(
new CallTypeHintPass(),
new ClassPass(),
new ClassNamePass(),
new InstanceMockPass(),
new InterfacePass(),
new MethodDefinitionPass(),
new RemoveUnserializeForInternalSerializableClassesPass(),
new RemoveBuiltinMethodsThatAreFinalPass(),
));
return new CachingGenerator($generator);
return new CachingGenerator(StringManipulationGenerator::withDefaultPasses());
}
/**
* Setter for the $_loader static property.
*
* @param Loader $loader
*/
public static function setLoader(Loader $loader)
@@ -220,6 +279,9 @@ class Mockery
}
/**
* Lazy loader method and getter for
* the $_loader property.
*
* @return Loader
*/
public static function getLoader()
@@ -232,6 +294,8 @@ class Mockery
}
/**
* Gets an EvalLoader to be used as default.
*
* @return EvalLoader
*/
public static function getDefaultLoader()
@@ -253,6 +317,8 @@ class Mockery
/**
* Reset the container to null.
*
* @return void
*/
public static function resetContainer()
{
@@ -269,10 +335,33 @@ class Mockery
return new \Mockery\Matcher\Any();
}
/**
* Return instance of AndAnyOtherArgs matcher.
*
* An alternative name to `andAnyOtherArgs` so
* the API stays closer to `any` as well.
*
* @return \Mockery\Matcher\AndAnyOtherArgs
*/
public static function andAnyOthers()
{
return new \Mockery\Matcher\AndAnyOtherArgs();
}
/**
* Return instance of AndAnyOtherArgs matcher.
*
* @return \Mockery\Matcher\AndAnyOtherArgs
*/
public static function andAnyOtherArgs()
{
return new \Mockery\Matcher\AndAnyOtherArgs();
}
/**
* Return instance of TYPE matcher.
*
* @param $expected
* @param mixed $expected
*
* @return \Mockery\Matcher\Type
*/
@@ -284,39 +373,44 @@ class Mockery
/**
* Return instance of DUCKTYPE matcher.
*
* @param array ...$args
*
* @return \Mockery\Matcher\Ducktype
*/
public static function ducktype()
public static function ducktype(...$args)
{
return new \Mockery\Matcher\Ducktype(func_get_args());
return new \Mockery\Matcher\Ducktype($args);
}
/**
* Return instance of SUBSET matcher.
*
* @param array $part
* @param bool $strict - (Optional) True for strict comparison, false for loose
*
* @return \Mockery\Matcher\Subset
*/
public static function subset(array $part)
public static function subset(array $part, $strict = true)
{
return new \Mockery\Matcher\Subset($part);
return new \Mockery\Matcher\Subset($part, $strict);
}
/**
* Return instance of CONTAINS matcher.
*
* @param mixed $args
*
* @return \Mockery\Matcher\Contains
*/
public static function contains()
public static function contains(...$args)
{
return new \Mockery\Matcher\Contains(func_get_args());
return new \Mockery\Matcher\Contains($args);
}
/**
* Return instance of HASKEY matcher.
*
* @param $key
* @param mixed $key
*
* @return \Mockery\Matcher\HasKey
*/
@@ -328,7 +422,7 @@ class Mockery
/**
* Return instance of HASVALUE matcher.
*
* @param $val
* @param mixed $val
*
* @return \Mockery\Matcher\HasValue
*/
@@ -340,7 +434,24 @@ class Mockery
/**
* Return instance of CLOSURE matcher.
*
* @param $closure
* @param $reference
*
* @return \Mockery\Matcher\Closure
*/
public static function capture(&$reference)
{
$closure = function ($argument) use (&$reference) {
$reference = $argument;
return true;
};
return new \Mockery\Matcher\Closure($closure);
}
/**
* Return instance of CLOSURE matcher.
*
* @param mixed $closure
*
* @return \Mockery\Matcher\Closure
*/
@@ -352,7 +463,7 @@ class Mockery
/**
* Return instance of MUSTBE matcher.
*
* @param $expected
* @param mixed $expected
*
* @return \Mockery\Matcher\MustBe
*/
@@ -364,7 +475,7 @@ class Mockery
/**
* Return instance of NOT matcher.
*
* @param $expected
* @param mixed $expected
*
* @return \Mockery\Matcher\Not
*/
@@ -376,25 +487,44 @@ class Mockery
/**
* Return instance of ANYOF matcher.
*
* @param array ...$args
*
* @return \Mockery\Matcher\AnyOf
*/
public static function anyOf()
public static function anyOf(...$args)
{
return new \Mockery\Matcher\AnyOf(func_get_args());
return new \Mockery\Matcher\AnyOf($args);
}
/**
* Return instance of NOTANYOF matcher.
*
* @param array ...$args
*
* @return \Mockery\Matcher\NotAnyOf
*/
public static function notAnyOf()
public static function notAnyOf(...$args)
{
return new \Mockery\Matcher\NotAnyOf(func_get_args());
return new \Mockery\Matcher\NotAnyOf($args);
}
/**
* Get the global configuration container.
* Return instance of PATTERN matcher.
*
* @param mixed $expected
*
* @return \Mockery\Matcher\Pattern
*/
public static function pattern($expected)
{
return new \Mockery\Matcher\Pattern($expected);
}
/**
* Lazy loader and Getter for the global
* configuration container.
*
* @return \Mockery\Configuration
*/
public static function getConfiguration()
{
@@ -427,8 +557,21 @@ class Mockery
return $method . '(' . implode(', ', $formattedArguments) . ')';
}
/**
* Gets the string representation
* of any passed argument.
*
* @param mixed $argument
* @param int $depth
*
* @return mixed
*/
private static function formatArgument($argument, $depth = 0)
{
if ($argument instanceof MatcherAbstract) {
return (string) $argument;
}
if (is_object($argument)) {
return 'object(' . get_class($argument) . ')';
}
@@ -439,16 +582,19 @@ class Mockery
if (is_array($argument)) {
if ($depth === 1) {
$argument = 'array(...)';
$argument = '[...]';
} else {
$sample = array();
foreach ($argument as $key => $value) {
$sample[$key] = self::formatArgument($value, $depth + 1);
$key = is_int($key) ? $key : "'$key'";
$value = self::formatArgument($value, $depth + 1);
$sample[] = "$key => $value";
}
$argument = preg_replace("{\s}", '', var_export($sample, true));
$argument = "[" . implode(", ", $sample) . "]";
}
return ((strlen($argument) > 1000) ? substr($argument, 0, 1000).'...)' : $argument);
return ((strlen($argument) > 1000) ? substr($argument, 0, 1000) . '...]' : $argument);
}
if (is_bool($argument)) {
@@ -463,9 +609,7 @@ class Mockery
return 'NULL';
}
$argument = (string) $argument;
return $depth === 0 ? '"' . $argument . '"' : $argument;
return "'" . (string) $argument . "'";
}
/**
@@ -507,7 +651,7 @@ class Mockery
/**
* Utility function to turn public properties and public get* and is* method values into an array.
*
* @param $object
* @param object $object
* @param int $nesting
*
* @return array
@@ -518,18 +662,29 @@ class Mockery
return array('...');
}
return array(
'class' => get_class($object),
'properties' => self::extractInstancePublicProperties($object, $nesting),
'getters' => self::extractGetters($object, $nesting)
$defaultFormatter = function ($object, $nesting) {
return array('properties' => self::extractInstancePublicProperties($object, $nesting));
};
$class = get_class($object);
$formatter = self::getConfiguration()->getObjectFormatter($class, $defaultFormatter);
$array = array(
'class' => $class,
'identity' => '#' . md5(spl_object_hash($object))
);
$array = array_merge($array, $formatter($object, $nesting));
return $array;
}
/**
* Returns all public instance properties.
*
* @param $object
* @param $nesting
* @param mixed $object
* @param int $nesting
*
* @return array
*/
@@ -542,7 +697,11 @@ class Mockery
foreach ($properties as $publicProperty) {
if (!$publicProperty->isStatic()) {
$name = $publicProperty->getName();
$cleanedProperties[$name] = self::cleanupNesting($object->$name, $nesting);
try {
$cleanedProperties[$name] = self::cleanupNesting($object->$name, $nesting);
} catch (\Exception $exception) {
$cleanedProperties[$name] = $exception->getMessage();
}
}
}
@@ -550,39 +709,14 @@ class Mockery
}
/**
* Returns all object getters.
* Utility method used for recursively generating
* an object or array representation.
*
* @param $object
* @param $nesting
* @param mixed $argument
* @param int $nesting
*
* @return array
* @return mixed
*/
private static function extractGetters($object, $nesting)
{
$reflection = new \ReflectionClass(get_class($object));
$publicMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
$getters = array();
foreach ($publicMethods as $publicMethod) {
$name = $publicMethod->getName();
$irrelevantName = (substr($name, 0, 3) !== 'get' && substr($name, 0, 2) !== 'is');
$isStatic = $publicMethod->isStatic();
$numberOfParameters = $publicMethod->getNumberOfParameters();
if ($irrelevantName || $numberOfParameters != 0 || $isStatic) {
continue;
}
try {
$getters[$name] = self::cleanupNesting($object->$name(), $nesting);
} catch (\Exception $e) {
$getters[$name] = '!! ' . get_class($e) . ': ' . $e->getMessage() . ' !!';
}
}
return $getters;
}
private static function cleanupNesting($argument, $nesting)
{
if (is_object($argument)) {
@@ -599,6 +733,16 @@ class Mockery
return $argument;
}
/**
* Utility method for recursively
* gerating a representation
* of the given array.
*
* @param array $argument
* @param int $nesting
*
* @return mixed
*/
private static function cleanupArray($argument, $nesting = 3)
{
if ($nesting == 0) {
@@ -620,12 +764,12 @@ class Mockery
* Utility function to parse shouldReceive() arguments and generate
* expectations from such as needed.
*
* @param Mockery\MockInterface $mock
* @param array $args
* @param Mockery\LegacyMockInterface $mock
* @param array ...$args
* @param callable $add
* @return \Mockery\CompositeExpectation
*/
public static function parseShouldReturnArgs(\Mockery\MockInterface $mock, $args, $add)
public static function parseShouldReturnArgs(\Mockery\LegacyMockInterface $mock, $args, $add)
{
$composite = new \Mockery\CompositeExpectation();
@@ -648,13 +792,13 @@ class Mockery
* Sets up expectations on the members of the CompositeExpectation and
* builds up any demeter chain that was passed to shouldReceive.
*
* @param \Mockery\MockInterface $mock
* @param \Mockery\LegacyMockInterface $mock
* @param string $arg
* @param callable $add
* @throws Mockery\Exception
* @return \Mockery\ExpectationDirector
* @return \Mockery\ExpectationInterface
*/
protected static function buildDemeterChain(\Mockery\MockInterface $mock, $arg, $add)
protected static function buildDemeterChain(\Mockery\LegacyMockInterface $mock, $arg, $add)
{
/** @var Mockery\Container $container */
$container = $mock->mockery_getContainer();
@@ -680,6 +824,8 @@ class Mockery
return $add($method);
};
$parent = get_class($mock);
while (true) {
$method = array_shift($methodNames);
$expectations = $mock->mockery_getExpectationsFor($method);
@@ -690,14 +836,16 @@ class Mockery
break;
}
$mock = self::getNewDemeterMock($container, $method, $expectations);
$mock = self::getNewDemeterMock($container, $parent, $method, $expectations);
} else {
$demeterMockKey = $container->getKeyOfDemeterMockFor($method);
$demeterMockKey = $container->getKeyOfDemeterMockFor($method, $parent);
if ($demeterMockKey) {
$mock = self::getExistingDemeterMock($container, $demeterMockKey);
}
}
$parent .= '->' . $method;
$nextExp = function ($n) use ($mock) {
return $mock->shouldReceive($n);
};
@@ -707,30 +855,66 @@ class Mockery
}
/**
* Gets a new demeter configured
* mock from the container.
*
* @param \Mockery\Container $container
* @param string $parent
* @param string $method
* @param Mockery\ExpectationInterface $exp
*
* @return \Mockery\Mock
*/
private static function getNewDemeterMock(Mockery\Container $container,
private static function getNewDemeterMock(
Mockery\Container $container,
$parent,
$method,
Mockery\ExpectationInterface $exp
) {
$mock = $container->mock('demeter_' . $method);
$newMockName = 'demeter_' . md5($parent) . '_' . $method;
$parRef = null;
$parRefMethod = null;
$parRefMethodRetType = null;
$parentMock = $exp->getMock();
if ($parentMock !== null) {
$parRef = new ReflectionObject($parentMock);
}
if ($parRef !== null && $parRef->hasMethod($method)) {
$parRefMethod = $parRef->getMethod($method);
$parRefMethodRetType = Reflector::getReturnType($parRefMethod, true);
if ($parRefMethodRetType !== null && $parRefMethodRetType !== 'mixed') {
$nameBuilder = new MockNameBuilder();
$nameBuilder->addPart('\\' . $newMockName);
$mock = self::namedMock($nameBuilder->build(), $parRefMethodRetType);
$exp->andReturn($mock);
return $mock;
}
}
$mock = $container->mock($newMockName);
$exp->andReturn($mock);
return $mock;
}
/**
* Gets an specific demeter mock from
* the ones kept by the container.
*
* @param \Mockery\Container $container
* @param string $demeterMockKey
*
* @return mixed
*/
private static function getExistingDemeterMock(Mockery\Container $container, $demeterMockKey)
{
private static function getExistingDemeterMock(
Mockery\Container $container,
$demeterMockKey
) {
$mocks = $container->getMocks();
$mock = $mocks[$demeterMockKey];
@@ -738,6 +922,9 @@ class Mockery
}
/**
* Checks if the passed array representing a demeter
* chain with the method names is empty.
*
* @param array $methodNames
*
* @return bool
@@ -747,6 +934,43 @@ class Mockery
return empty($methodNames);
}
public static function declareClass($fqn)
{
return static::declareType($fqn, "class");
}
public static function declareInterface($fqn)
{
return static::declareType($fqn, "interface");
}
private static function declareType($fqn, $type)
{
$targetCode = "<?php ";
$shortName = $fqn;
if (strpos($fqn, "\\")) {
$parts = explode("\\", $fqn);
$shortName = trim(array_pop($parts));
$namespace = implode("\\", $parts);
$targetCode.= "namespace $namespace;\n";
}
$targetCode.= "$type $shortName {} ";
/*
* We could eval here, but it doesn't play well with the way
* PHPUnit tries to backup global state and the require definition
* loader
*/
$tmpfname = tempnam(sys_get_temp_dir(), "Mockery");
file_put_contents($tmpfname, $targetCode);
require $tmpfname;
\Mockery::registerFileForCleanUp($tmpfname);
}
/**
* Register a file to be deleted on tearDown.
*

View File

@@ -1,26 +1,90 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Adapter\Phpunit;
use Mockery;
/**
* Integrates Mockery into PHPUnit. Ensures Mockery expectations are verified
* for each test and are included by the assertion counter.
*/
trait MockeryPHPUnitIntegration
{
use MockeryPHPUnitIntegrationAssertPostConditions;
protected $mockeryOpen;
/**
* Performs assertions shared by all tests of a test case. This method is
* called before execution of a test ends and before the tearDown method.
*/
protected function assertPostConditions()
protected function mockeryAssertPostConditions()
{
parent::assertPostConditions();
$this->addMockeryExpectationsToAssertionCount();
$this->checkMockeryExceptions();
$this->closeMockery();
// Add Mockery expectations to assertion count.
if (($container = \Mockery::getContainer()) !== null) {
$this->addToAssertionCount($container->mockery_getExpectationCount());
parent::assertPostConditions();
}
protected function addMockeryExpectationsToAssertionCount()
{
$this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());
}
protected function checkMockeryExceptions()
{
if (!method_exists($this, "markAsRisky")) {
return;
}
// Verify Mockery expectations.
\Mockery::close();
foreach (Mockery::getContainer()->mockery_thrownExceptions() as $e) {
if (!$e->dismissed()) {
$this->markAsRisky();
}
}
}
protected function closeMockery()
{
Mockery::close();
$this->mockeryOpen = false;
}
/**
* @before
*/
protected function startMockery()
{
$this->mockeryOpen = true;
}
/**
* @after
*/
protected function purgeMockeryContainer()
{
if ($this->mockeryOpen) {
// post conditions wasn't called, so test probably failed
Mockery::close();
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2019 Enalean
* @license https://github.com/mockery/mockery/blob/master/LICENSE New BSD License
*/
declare(strict_types=1);
namespace Mockery\Adapter\Phpunit;
trait MockeryPHPUnitIntegrationAssertPostConditions
{
protected function assertPostConditions(): void
{
$this->mockeryAssertPostConditions();
}
}

View File

@@ -1,30 +1,35 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Adapter\Phpunit;
use Mockery;
abstract class MockeryTestCase extends \PHPUnit_Framework_TestCase
abstract class MockeryTestCase extends \PHPUnit\Framework\TestCase
{
protected function assertPostConditions()
{
$this->addMockeryExpectationsToAssertionCount();
$this->closeMockery();
use MockeryPHPUnitIntegration;
use MockeryTestCaseSetUp;
parent::assertPostConditions();
protected function mockeryTestSetUp()
{
}
protected function addMockeryExpectationsToAssertionCount()
protected function mockeryTestTearDown()
{
$container = Mockery::getContainer();
if ($container != null) {
$count = $container->mockery_getExpectationCount();
$this->addToAssertionCount($count);
}
}
protected function closeMockery()
{
Mockery::close();
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2019 Enalean
* @license https://github.com/mockery/mockery/blob/master/LICENSE New BSD License
*/
declare(strict_types=1);
namespace Mockery\Adapter\Phpunit;
trait MockeryTestCaseSetUp
{
protected function setUp(): void
{
parent::setUp();
$this->mockeryTestSetUp();
}
protected function tearDown(): void
{
$this->mockeryTestTearDown();
parent::tearDown();
}
}

View File

@@ -12,82 +12,37 @@
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Adapter\Phpunit;
class TestListener implements \PHPUnit_Framework_TestListener
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\TestListener as PHPUnitTestListener;
class TestListener implements PHPUnitTestListener
{
use TestListenerDefaultImplementation;
/**
* After each test, perform Mockery verification tasks and cleanup the
* statically stored Mockery container for the next test.
*
* @param PHPUnit_Framework_Test $test
* @param float $time
*/
public function endTest(\PHPUnit_Framework_Test $test, $time)
private $trait;
public function __construct()
{
try {
$container = \Mockery::getContainer();
// check addToAssertionCount is important to avoid mask test errors
if ($container != null && method_exists($test, 'addToAssertionCount')) {
$expectation_count = $container->mockery_getExpectationCount();
$test->addToAssertionCount($expectation_count);
}
\Mockery::close();
} catch (\Exception $e) {
$result = $test->getTestResultObject();
$result->addError($test, $e, $time);
}
$this->trait = new TestListenerTrait();
}
/**
* Add Mockery files to PHPUnit's blacklist so they don't showup on coverage reports
*/
public function startTestSuite(\PHPUnit_Framework_TestSuite $suite)
{
if (class_exists('\\PHP_CodeCoverage_Filter')
&& method_exists('\\PHP_CodeCoverage_Filter', 'getInstance')) {
\PHP_CodeCoverage_Filter::getInstance()->addDirectoryToBlacklist(
__DIR__.'/../../../Mockery/', '.php', '', 'PHPUNIT'
);
\PHP_CodeCoverage_Filter::getInstance()->addFileToBlacklist(__DIR__.'/../../../Mockery.php', 'PHPUNIT');
}
}
/**
* The Listening methods below are not required for Mockery
*/
public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time)
public function endTest(Test $test, float $time): void
{
$this->trait->endTest($test, $time);
}
public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time)
{
}
public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
{
}
public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
{
}
public function addRiskyTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
{
}
public function endTestSuite(\PHPUnit_Framework_TestSuite $suite)
{
}
public function startTest(\PHPUnit_Framework_Test $test)
public function startTestSuite(TestSuite $suite): void
{
$this->trait->startTestSuite();
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Adapter\Phpunit;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Util\Blacklist;
use PHPUnit\Runner\BaseTestRunner;
class TestListenerTrait
{
/**
* endTest is called after each test and checks if \Mockery::close() has
* been called, and will let the test fail if it hasn't.
*
* @param Test $test
* @param float $time
*/
public function endTest(Test $test, $time)
{
if (!$test instanceof TestCase) {
// We need the getTestResultObject and getStatus methods which are
// not part of the interface.
return;
}
if ($test->getStatus() !== BaseTestRunner::STATUS_PASSED) {
// If the test didn't pass there is no guarantee that
// verifyMockObjects and assertPostConditions have been called.
// And even if it did, the point here is to prevent false
// negatives, not to make failing tests fail for more reasons.
return;
}
try {
// The self() call is used as a sentinel. Anything that throws if
// the container is closed already will do.
\Mockery::self();
} catch (\LogicException $_) {
return;
}
$e = new ExpectationFailedException(
\sprintf(
"Mockery's expectations have not been verified. Make sure that \Mockery::close() is called at the end of the test. Consider using %s\MockeryPHPUnitIntegration or extending %s\MockeryTestCase.",
__NAMESPACE__,
__NAMESPACE__
)
);
/** @var \PHPUnit\Framework\TestResult $result */
$result = $test->getTestResultObject();
if ($result !== null) {
$result->addFailure($test, $e, $time);
}
}
public function startTestSuite()
{
if (method_exists(Blacklist::class, 'addDirectory')) {
(new BlackList())->getBlacklistedDirectories();
Blacklist::addDirectory(\dirname((new \ReflectionClass(\Mockery::class))->getFileName()));
} else {
Blacklist::$blacklistedClassNames[\Mockery::class] = 1;
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2017 Dave Marshall https://github.com/davedevelopment
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery;
use Mockery\Matcher\Closure;
/**
* @internal
*/
class ClosureWrapper
{
private $closure;
public function __construct(\Closure $closure)
{
$this->closure = $closure;
}
public function __invoke()
{
return call_user_func_array($this->closure, func_get_args());
}
}

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -22,7 +22,6 @@ namespace Mockery;
class CompositeExpectation implements ExpectationInterface
{
/**
* Stores an array of all expectations for this composite
*
@@ -42,11 +41,22 @@ class CompositeExpectation implements ExpectationInterface
}
/**
* @param mixed ...
* @param mixed ...$args
*/
public function andReturn()
public function andReturn(...$args)
{
return $this->__call(__FUNCTION__, func_get_args());
return $this->__call(__FUNCTION__, $args);
}
/**
* Set a return value, or sequential queue of return values
*
* @param mixed ...$args
* @return self
*/
public function andReturns(...$args)
{
return call_user_func_array([$this, 'andReturn'], $args);
}
/**
@@ -79,7 +89,7 @@ class CompositeExpectation implements ExpectationInterface
/**
* Return the parent mock of the first expectation
*
* @return \Mockery\MockInterface
* @return \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
public function getMock()
{
@@ -91,7 +101,7 @@ class CompositeExpectation implements ExpectationInterface
/**
* Mockery API alias to getMock
*
* @return \Mockery\MockInterface
* @return \Mockery\LegacyMockInterface|\Mockery\MockInterface
*/
public function mock()
{
@@ -102,17 +112,30 @@ class CompositeExpectation implements ExpectationInterface
* Starts a new expectation addition on the first mock which is the primary
* target outside of a demeter chain
*
* @param mixed ...
* @param mixed ...$args
* @return \Mockery\Expectation
*/
public function shouldReceive()
public function shouldReceive(...$args)
{
$args = func_get_args();
reset($this->_expectations);
$first = current($this->_expectations);
return call_user_func_array(array($first->getMock(), 'shouldReceive'), $args);
}
/**
* Starts a new expectation addition on the first mock which is the primary
* target outside of a demeter chain
*
* @param mixed ...$args
* @return \Mockery\Expectation
*/
public function shouldNotReceive(...$args)
{
reset($this->_expectations);
$first = current($this->_expectations);
return call_user_func_array(array($first->getMock(), 'shouldNotReceive'), $args);
}
/**
* Return the string summary of this composite expectation
*

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -22,7 +22,6 @@ namespace Mockery;
class Configuration
{
/**
* Boolean assertion of whether we can mock methods which do not actually
* exist for the given class or object (ignored for unreal mocks)
@@ -41,6 +40,11 @@ class Configuration
*/
protected $_allowMockingMethodsUnnecessarily = true;
/**
* @var QuickDefinitionsConfiguration
*/
protected $_quickDefinitionsConfiguration;
/**
* Parameter map for use with PHP internal classes.
*
@@ -48,10 +52,39 @@ class Configuration
*/
protected $_internalClassParamMap = array();
protected $_constantsMap = array();
/**
* Boolean assertion is reflection caching enabled or not. It should be
* always enabled, except when using PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*/
protected $_reflectionCacheEnabled = true;
public function __construct()
{
$this->_quickDefinitionsConfiguration = new QuickDefinitionsConfiguration();
}
/**
* Custom object formatters
*
* @var array
*/
protected $_objectFormatters = array();
/**
* Default argument matchers
*
* @var array
*/
protected $_defaultMatchers = array();
/**
* Set boolean to allow/prevent mocking of non-existent methods
*
* @param bool
* @param bool $flag
*/
public function allowMockingNonExistentMethods($flag = true)
{
@@ -71,10 +104,14 @@ class Configuration
/**
* Set boolean to allow/prevent unnecessary mocking of methods
*
* @param bool
* @param bool $flag
*
* @deprecated since 1.4.0
*/
public function allowMockingMethodsUnnecessarily($flag = true)
{
@trigger_error(sprintf("The %s method is deprecated and will be removed in a future version of Mockery", __METHOD__), E_USER_DEPRECATED);
$this->_allowMockingMethodsUnnecessarily = (bool) $flag;
}
@@ -82,9 +119,13 @@ class Configuration
* Return flag indicating whether mocking non-existent methods allowed
*
* @return bool
*
* @deprecated since 1.4.0
*/
public function mockingMethodsUnnecessarilyAllowed()
{
@trigger_error(sprintf("The %s method is deprecated and will be removed in a future version of Mockery", __METHOD__), E_USER_DEPRECATED);
return $this->_allowMockingMethodsUnnecessarily;
}
@@ -98,6 +139,10 @@ class Configuration
*/
public function setInternalClassMethodParamMap($class, $method, array $map)
{
if (\PHP_MAJOR_VERSION > 7) {
throw new \LogicException('Internal class parameter overriding is not available in PHP 8. Incompatible signatures have been reclassified as fatal errors.');
}
if (!isset($this->_internalClassParamMap[strtolower($class)])) {
$this->_internalClassParamMap[strtolower($class)] = array();
}
@@ -105,7 +150,7 @@ class Configuration
}
/**
* Remove all overriden parameter maps from internal PHP classes.
* Remove all overridden parameter maps from internal PHP classes.
*/
public function resetInternalClassMethodParamMaps()
{
@@ -115,7 +160,7 @@ class Configuration
/**
* Get the parameter map of an internal PHP class method
*
* @return array
* @return array|null
*/
public function getInternalClassMethodParamMap($class, $method)
{
@@ -128,4 +173,111 @@ class Configuration
{
return $this->_internalClassParamMap;
}
public function setConstantsMap(array $map)
{
$this->_constantsMap = $map;
}
public function getConstantsMap()
{
return $this->_constantsMap;
}
/**
* Returns the quick definitions configuration
*/
public function getQuickDefinitions(): QuickDefinitionsConfiguration
{
return $this->_quickDefinitionsConfiguration;
}
/**
* Disable reflection caching
*
* It should be always enabled, except when using
* PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*/
public function disableReflectionCache()
{
$this->_reflectionCacheEnabled = false;
}
/**
* Enable reflection caching
*
* It should be always enabled, except when using
* PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*/
public function enableReflectionCache()
{
$this->_reflectionCacheEnabled = true;
}
/**
* Is reflection cache enabled?
*/
public function reflectionCacheEnabled()
{
return $this->_reflectionCacheEnabled;
}
public function setObjectFormatter($class, $formatterCallback)
{
$this->_objectFormatters[$class] = $formatterCallback;
}
public function getObjectFormatter($class, $defaultFormatter)
{
$parentClass = $class;
do {
$classes[] = $parentClass;
$parentClass = get_parent_class($parentClass);
} while ($parentClass);
$classesAndInterfaces = array_merge($classes, class_implements($class));
foreach ($classesAndInterfaces as $type) {
if (isset($this->_objectFormatters[$type])) {
return $this->_objectFormatters[$type];
}
}
return $defaultFormatter;
}
/**
* @param string $class
* @param string $matcherClass
*/
public function setDefaultMatcher($class, $matcherClass)
{
if (!is_a($matcherClass, \Mockery\Matcher\MatcherAbstract::class, true) &&
!is_a($matcherClass, \Hamcrest\Matcher::class, true) &&
!is_a($matcherClass, \Hamcrest_Matcher::class, true)
) {
throw new \InvalidArgumentException(
"Matcher class must be either Hamcrest matcher or extend \Mockery\Matcher\MatcherAbstract, " .
"'$matcherClass' given."
);
}
$this->_defaultMatchers[$class] = $matcherClass;
}
public function getDefaultMatcher($class)
{
$parentClass = $class;
do {
$classes[] = $parentClass;
$parentClass = get_parent_class($parentClass);
} while ($parentClass);
$classesAndInterfaces = array_merge($classes, class_implements($class));
foreach ($classesAndInterfaces as $type) {
if (isset($this->_defaultMatchers[$type])) {
return $this->_defaultMatchers[$type];
}
}
return null;
}
}

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -57,7 +57,7 @@ class Container
protected $_groups = array();
/**
* @var Generator\Generator
* @var Generator
*/
protected $_generator;
@@ -85,17 +85,18 @@ class Container
* names or partials - just so long as it's something that can be mocked.
* I'll refactor it one day so it's easier to follow.
*
* @param array ...$args
*
* @return Mock
* @throws Exception\RuntimeException
* @throws Exception
* @return \Mockery\Mock
*/
public function mock()
public function mock(...$args)
{
$expectationClosure = null;
$quickdefs = array();
$constructorArgs = null;
$blocks = array();
$args = func_get_args();
$class = null;
if (count($args) > 1) {
$finalArg = end($args);
@@ -116,87 +117,83 @@ class Container
reset($args);
$builder->setParameterOverrides(\Mockery::getConfiguration()->getInternalClassMethodParamMaps());
$builder->setConstantsMap(\Mockery::getConfiguration()->getConstantsMap());
while (count($args) > 0) {
$arg = current($args);
$arg = array_shift($args);
// check for multiple interfaces
if (is_string($arg) && strpos($arg, ',') && !strpos($arg, ']')) {
$interfaces = explode(',', str_replace(' ', '', $arg));
foreach ($interfaces as $i) {
if (!interface_exists($i, true) && !class_exists($i, true)) {
throw new \Mockery\Exception(
'Class name follows the format for defining multiple'
. ' interfaces, however one or more of the interfaces'
. ' do not exist or are not included, or the base class'
. ' (which you may omit from the mock definition) does not exist'
);
if (is_string($arg)) {
foreach (explode('|', $arg) as $type) {
if ($arg === 'null') {
// skip PHP 8 'null's
} elseif (strpos($type, ',') && !strpos($type, ']')) {
$interfaces = explode(',', str_replace(' ', '', $type));
$builder->addTargets($interfaces);
} elseif (substr($type, 0, 6) == 'alias:') {
$type = str_replace('alias:', '', $type);
$builder->addTarget('stdClass');
$builder->setName($type);
} elseif (substr($type, 0, 9) == 'overload:') {
$type = str_replace('overload:', '', $type);
$builder->setInstanceMock(true);
$builder->addTarget('stdClass');
$builder->setName($type);
} elseif (substr($type, strlen($type)-1, 1) == ']') {
$parts = explode('[', $type);
if (!class_exists($parts[0], true) && !interface_exists($parts[0], true)) {
throw new \Mockery\Exception('Can only create a partial mock from'
. ' an existing class or interface');
}
$class = $parts[0];
$parts[1] = str_replace(' ', '', $parts[1]);
$partialMethods = array_filter(explode(',', strtolower(rtrim($parts[1], ']'))));
$builder->addTarget($class);
foreach ($partialMethods as $partialMethod) {
if ($partialMethod[0] === '!') {
$builder->addBlackListedMethod(substr($partialMethod, 1));
continue;
}
$builder->addWhiteListedMethod($partialMethod);
}
} elseif (class_exists($type, true) || interface_exists($type, true) || trait_exists($type, true)) {
$builder->addTarget($type);
} elseif (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed() && (!class_exists($type, true) && !interface_exists($type, true))) {
throw new \Mockery\Exception("Mockery can't find '$type' so can't mock it");
} else {
if (!$this->isValidClassName($type)) {
throw new \Mockery\Exception('Class name contains invalid characters');
}
$builder->addTarget($type);
}
break; // unions are "sum" types and not "intersections", and so we must only process the first part
}
$builder->addTargets($interfaces);
array_shift($args);
continue;
} elseif (is_string($arg) && substr($arg, 0, 6) == 'alias:') {
$name = array_shift($args);
$name = str_replace('alias:', '', $name);
$builder->addTarget('stdClass');
$builder->setName($name);
continue;
} elseif (is_string($arg) && substr($arg, 0, 9) == 'overload:') {
$name = array_shift($args);
$name = str_replace('overload:', '', $name);
$builder->setInstanceMock(true);
$builder->addTarget('stdClass');
$builder->setName($name);
continue;
} elseif (is_string($arg) && substr($arg, strlen($arg)-1, 1) == ']') {
$parts = explode('[', $arg);
if (!class_exists($parts[0], true) && !interface_exists($parts[0], true)) {
throw new \Mockery\Exception('Can only create a partial mock from'
. ' an existing class or interface');
}
$class = $parts[0];
$parts[1] = str_replace(' ', '', $parts[1]);
$partialMethods = explode(',', strtolower(rtrim($parts[1], ']')));
$builder->addTarget($class);
$builder->setWhiteListedMethods($partialMethods);
array_shift($args);
continue;
} elseif (is_string($arg) && (class_exists($arg, true) || interface_exists($arg, true))) {
$class = array_shift($args);
$builder->addTarget($class);
continue;
} elseif (is_string($arg)) {
$class = array_shift($args);
$builder->addTarget($class);
continue;
} elseif (is_object($arg)) {
$partial = array_shift($args);
$builder->addTarget($partial);
continue;
} elseif (is_array($arg) && !empty($arg) && array_keys($arg) !== range(0, count($arg) - 1)) {
// if associative array
if (array_key_exists(self::BLOCKS, $arg)) {
$blocks = $arg[self::BLOCKS];
}
unset($arg[self::BLOCKS]);
$quickdefs = array_shift($args);
continue;
$builder->addTarget($arg);
} elseif (is_array($arg)) {
$constructorArgs = array_shift($args);
continue;
if (!empty($arg) && array_keys($arg) !== range(0, count($arg) - 1)) {
// if associative array
if (array_key_exists(self::BLOCKS, $arg)) {
$blocks = $arg[self::BLOCKS];
}
unset($arg[self::BLOCKS]);
$quickdefs = $arg;
} else {
$constructorArgs = $arg;
}
} else {
throw new \Mockery\Exception(
'Unable to parse arguments sent to '
. get_class($this) . '::mock()'
);
}
throw new \Mockery\Exception(
'Unable to parse arguments sent to '
. get_class($this) . '::mock()'
);
}
$builder->addBlackListedMethods($blocks);
if (!is_null($constructorArgs)) {
$builder->addBlackListedMethod("__construct"); // we need to pass through
} else {
$builder->setMockOriginalDestructor(true);
}
if (!empty($partialMethods) && $constructorArgs === null) {
@@ -211,7 +208,7 @@ class Container
if (class_exists($def->getClassName(), $attemptAutoload = false)) {
$rfc = new \ReflectionClass($def->getClassName());
if (!$rfc->implementsInterface("Mockery\MockInterface")) {
if (!$rfc->implementsInterface("Mockery\LegacyMockInterface")) {
throw new \Mockery\Exception\RuntimeException("Could not load mock {$def->getClassName()}, class already exists");
}
}
@@ -219,10 +216,14 @@ class Container
$this->getLoader()->load($def);
$mock = $this->_getInstance($def->getClassName(), $constructorArgs);
$mock->mockery_init($this, $config->getTargetObject());
$mock->mockery_init($this, $config->getTargetObject(), $config->isInstanceMock());
if (!empty($quickdefs)) {
$mock->shouldReceive($quickdefs)->byDefault();
if (\Mockery::getConfiguration()->getQuickDefinitions()->shouldBeCalledAtLeastOnce()) {
$mock->shouldReceive($quickdefs)->atLeast()->once();
} else {
$mock->shouldReceive($quickdefs)->byDefault();
}
}
if (!empty($expectationClosure)) {
$expectationClosure($mock);
@@ -247,12 +248,13 @@ class Container
/**
* @param string $method
* @param string $parent
* @return string|null
*/
public function getKeyOfDemeterMockFor($method)
public function getKeyOfDemeterMockFor($method, $parent)
{
$keys = array_keys($this->_mocks);
$match = preg_grep("/__demeter_{$method}$/", $keys);
$match = preg_grep("/__demeter_" . md5($parent) . "_{$method}$/", $keys);
if (count($match) == 1) {
$res = array_values($match);
if (count($res) > 0) {
@@ -298,6 +300,22 @@ class Container
}
}
/**
* Retrieves all exceptions thrown by mocks
*
* @return array
*/
public function mockery_thrownExceptions()
{
$e = [];
foreach ($this->_mocks as $mock) {
$e = array_merge($e, $mock->mockery_thrownExceptions());
}
return $e;
}
/**
* Reset the container to its original state
*
@@ -373,7 +391,7 @@ class Container
* @throws \Mockery\Exception
* @return void
*/
public function mockery_validateOrder($method, $order, \Mockery\MockInterface $mock)
public function mockery_validateOrder($method, $order, \Mockery\LegacyMockInterface $mock)
{
if ($order < $this->_currentOrder) {
$exception = new \Mockery\Exception\InvalidOrderException(
@@ -406,10 +424,10 @@ class Container
/**
* Store a mock and set its container reference
*
* @param \Mockery\Mock
* @return \Mockery\Mock
* @param \Mockery\Mock $mock
* @return \Mockery\LegacyMockInterface|\Mockery\MockInterface
*/
public function rememberMock(\Mockery\MockInterface $mock)
public function rememberMock(\Mockery\LegacyMockInterface $mock)
{
if (!isset($this->_mocks[get_class($mock)])) {
$this->_mocks[get_class($mock)] = $mock;
@@ -459,7 +477,7 @@ class Container
}
try {
$instantiator = new Instantiator;
$instantiator = new Instantiator();
$instance = $instantiator->instantiate($mockName);
} catch (\Exception $ex) {
$internalMockName = $mockName . '_Internal';
@@ -476,31 +494,6 @@ class Container
return $instance;
}
/**
* Takes a class name and declares it
*
* @param string $fqcn
*/
public function declareClass($fqcn)
{
if (false !== strpos($fqcn, '/')) {
throw new \Mockery\Exception(
'Class name contains a forward slash instead of backslash needed '
. 'when employing namespaces'
);
}
if (false !== strpos($fqcn, "\\")) {
$parts = array_filter(explode("\\", $fqcn), function ($part) {
return $part !== "";
});
$cl = array_pop($parts);
$ns = implode("\\", $parts);
eval(" namespace $ns { class $cl {} }");
} else {
eval(" class $fqcn {} ");
}
}
protected function checkForNamedMockClashes($config)
{
$name = $config->getName();
@@ -521,4 +514,22 @@ class Container
$this->_namedMocks[$name] = $hash;
}
/**
* see http://php.net/manual/en/language.oop5.basic.php
* @param string $className
* @return bool
*/
public function isValidClassName($className)
{
$pos = strpos($className, '\\');
if ($pos === 0) {
$className = substr($className, 1); // remove the first backslash
}
// all the namespaces and class name should match the regex
$invalidNames = array_filter(explode('\\', $className), function ($name) {
return !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name);
});
return empty($invalidNames);
}
}

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -24,7 +24,6 @@ use Mockery;
class AtLeast extends CountValidatorAbstract
{
/**
* Checks if the validator can accept an additional nth call
*

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -24,7 +24,6 @@ use Mockery;
class AtMost extends CountValidatorAbstract
{
/**
* Validate the call count against this validator
*

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -22,7 +22,6 @@ namespace Mockery\CountValidator;
abstract class CountValidatorAbstract
{
/**
* Expectation for which this validator is assigned
*

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -24,7 +24,6 @@ use Mockery;
class Exact extends CountValidatorAbstract
{
/**
* Validate the call count against this validator
*
@@ -34,12 +33,15 @@ class Exact extends CountValidatorAbstract
public function validate($n)
{
if ($this->_limit !== $n) {
$because = $this->_expectation->getExceptionMessage();
$exception = new Mockery\Exception\InvalidCountException(
'Method ' . (string) $this->_expectation
. ' from ' . $this->_expectation->getMock()->mockery_getName()
. ' should be called' . PHP_EOL
. ' exactly ' . $this->_limit . ' times but called ' . $n
. ' times.'
. ($because ? ' Because ' . $this->_expectation->getExceptionMessage() : '')
);
$exception->setMock($this->_expectation->getMock())
->setMethodName((string) $this->_expectation)

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/

View File

@@ -0,0 +1,23 @@
<?php
namespace Mockery\Exception;
class BadMethodCallException extends \BadMethodCallException
{
private $dismissed = false;
public function dismiss()
{
$this->dismissed = true;
// we sometimes stack them
if ($this->getPrevious() && $this->getPrevious() instanceof BadMethodCallException) {
$this->getPrevious()->dismiss();
}
}
public function dismissed()
{
return $this->dismissed;
}
}

View File

@@ -7,21 +7,19 @@
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/master/LICENSE
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @subpackage UnitTests
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace test\Mockery\Fixtures;
namespace Mockery\Exception;
interface VoidMethod
class InvalidArgumentException extends \InvalidArgumentException
{
public function foo(): void;
}

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -25,7 +25,6 @@ use Mockery\Exception\RuntimeException;
class InvalidCountException extends Mockery\CountValidator\Exception
{
protected $method = null;
protected $expected = 0;
@@ -36,7 +35,7 @@ class InvalidCountException extends Mockery\CountValidator\Exception
protected $mockObject = null;
public function setMock(Mockery\MockInterface $mock)
public function setMock(Mockery\LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -24,7 +24,6 @@ use Mockery;
class InvalidOrderException extends Mockery\Exception
{
protected $method = null;
protected $expected = 0;
@@ -33,7 +32,7 @@ class InvalidOrderException extends Mockery\Exception
protected $mockObject = null;
public function setMock(Mockery\MockInterface $mock)
public function setMock(Mockery\LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -24,14 +24,13 @@ use Mockery;
class NoMatchingExpectationException extends Mockery\Exception
{
protected $method = null;
protected $actual = array();
protected $mockObject = null;
public function setMock(Mockery\MockInterface $mock)
public function setMock(Mockery\LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/

View File

@@ -14,19 +14,25 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery;
use Closure;
use Mockery\Matcher\NoArgs;
use Mockery\Matcher\AnyArgs;
use Mockery\Matcher\AndAnyOtherArgs;
use Mockery\Matcher\ArgumentListMatcher;
use Mockery\Matcher\MultiArgumentClosure;
class Expectation implements ExpectationInterface
{
/**
* Mock object to which this expectation belongs
*
* @var object
* @var \Mockery\LegacyMockInterface
*/
protected $_mock = null;
@@ -37,6 +43,13 @@ class Expectation implements ExpectationInterface
*/
protected $_name = null;
/**
* Exception message
*
* @var string|null
*/
protected $_because = null;
/**
* Arguments expected by this expectation
*
@@ -123,13 +136,6 @@ class Expectation implements ExpectationInterface
*/
protected $_globally = false;
/**
* Flag indicating we expect no arguments
*
* @var bool
*/
protected $_noArgsExpectation = false;
/**
* Flag indicating if the return value should be obtained from the original
* class method instead of returning predefined values from the return queue
@@ -141,13 +147,14 @@ class Expectation implements ExpectationInterface
/**
* Constructor
*
* @param \Mockery\MockInterface $mock
* @param \Mockery\LegacyMockInterface $mock
* @param string $name
*/
public function __construct(\Mockery\MockInterface $mock, $name)
public function __construct(\Mockery\LegacyMockInterface $mock, $name)
{
$this->_mock = $mock;
$this->_name = $name;
$this->withAnyArgs();
}
/**
@@ -176,12 +183,31 @@ class Expectation implements ExpectationInterface
if (true === $this->_passthru) {
return $this->_mock->mockery_callSubjectMethod($this->_name, $args);
}
$return = $this->_getReturnValue($args);
if ($return instanceof \Exception && $this->_throw === true) {
$this->throwAsNecessary($return);
$this->_setValues();
return $return;
}
/**
* Throws an exception if the expectation has been configured to do so
*
* @throws \Throwable
* @return void
*/
private function throwAsNecessary($return)
{
if (!$this->_throw) {
return;
}
if ($return instanceof \Throwable) {
throw $return;
}
$this->_setValues();
return $return;
return;
}
/**
@@ -192,10 +218,19 @@ class Expectation implements ExpectationInterface
*/
protected function _setValues()
{
$mockClass = get_class($this->_mock);
$container = $this->_mock->mockery_getContainer();
/** @var Mock[] $mocks */
$mocks = $container->getMocks();
foreach ($this->_setQueue as $name => &$values) {
if (count($values) > 0) {
$value = array_shift($values);
$this->_mock->{$name} = $value;
foreach ($mocks as $mock) {
if (is_a($mock, $mockClass) && $mock->mockery_isInstance()) {
$mock->{$name} = $value;
}
}
}
}
}
@@ -218,32 +253,7 @@ class Expectation implements ExpectationInterface
return current($this->_returnQueue);
}
$rm = $this->_mock->mockery_getMethod($this->_name);
if ($rm && version_compare(PHP_VERSION, '7.0.0-dev') >= 0 && $rm->hasReturnType()) {
$type = (string) $rm->getReturnType();
switch ($type) {
case '': return;
case 'void': return;
case 'string': return '';
case 'int': return 0;
case 'float': return 0.0;
case 'bool': return false;
case 'array': return array();
case 'callable':
case 'Closure':
return function () {};
case 'Traversable':
case 'Generator':
// Remove eval() when minimum version >=5.5
$generator = eval('return function () { yield; };');
return $generator();
default:
return \Mockery::mock($type);
}
}
return $this->_mock->mockery_returnValueForMethod($this->_name);
}
/**
@@ -290,7 +300,7 @@ class Expectation implements ExpectationInterface
/**
* Verify this expectation
*
* @return bool
* @return void
*/
public function verify()
{
@@ -299,6 +309,20 @@ class Expectation implements ExpectationInterface
}
}
/**
* Check if the registered expectation is an ArgumentListMatcher
* @return bool
*/
private function isArgumentListMatcher()
{
return (count($this->_expectedArgs) === 1 && ($this->_expectedArgs[0] instanceof ArgumentListMatcher));
}
private function isAndAnyOtherArgumentsMatcher($expectedArg)
{
return $expectedArg instanceof AndAnyOtherArgs;
}
/**
* Check if passed arguments match an argument expectation
*
@@ -307,12 +331,33 @@ class Expectation implements ExpectationInterface
*/
public function matchArgs(array $args)
{
if (empty($this->_expectedArgs) && !$this->_noArgsExpectation) {
return true;
if ($this->isArgumentListMatcher()) {
return $this->_matchArg($this->_expectedArgs[0], $args);
}
if (count($args) !== count($this->_expectedArgs)) {
$argCount = count($args);
if ($argCount !== count((array) $this->_expectedArgs)) {
$lastExpectedArgument = end($this->_expectedArgs);
reset($this->_expectedArgs);
if ($this->isAndAnyOtherArgumentsMatcher($lastExpectedArgument)) {
$args = array_slice($args, 0, array_search($lastExpectedArgument, $this->_expectedArgs, true));
return $this->_matchArgs($args);
}
return false;
}
return $this->_matchArgs($args);
}
/**
* Check if the passed arguments match the expectations, one by one.
*
* @param array $args
* @return bool
*/
protected function _matchArgs($args)
{
$argCount = count($args);
for ($i=0; $i<$argCount; $i++) {
$param =& $args[$i];
@@ -320,14 +365,14 @@ class Expectation implements ExpectationInterface
return false;
}
}
return true;
}
/**
* Check if passed argument matches an argument expectation
*
* @param array $args
* @param mixed $expected
* @param mixed $actual
* @return bool
*/
protected function _matchArg($expected, &$actual)
@@ -338,26 +383,22 @@ class Expectation implements ExpectationInterface
if (!is_object($expected) && !is_object($actual) && $expected == $actual) {
return true;
}
if (is_string($expected) && !is_array($actual) && !is_object($actual)) {
# push/pop an error handler here to to make sure no error/exception thrown if $expected is not a regex
set_error_handler(function () {});
$result = preg_match($expected, (string) $actual);
restore_error_handler();
if ($result) {
return true;
}
}
if (is_string($expected) && is_object($actual)) {
$result = $actual instanceof $expected;
if ($result) {
return true;
}
}
if (is_object($expected)) {
$matcher = \Mockery::getConfiguration()->getDefaultMatcher(get_class($expected));
if ($matcher !== null) {
$expected = new $matcher($expected);
}
}
if ($expected instanceof \Mockery\Matcher\MatcherAbstract) {
return $expected->match($actual);
}
if (is_a($expected, '\Hamcrest\Matcher') || is_a($expected, '\Hamcrest_Matcher')) {
if ($expected instanceof \Hamcrest\Matcher || $expected instanceof \Hamcrest_Matcher) {
return $expected->matches($actual);
}
return false;
@@ -366,27 +407,59 @@ class Expectation implements ExpectationInterface
/**
* Expected argument setter for the expectation
*
* @param mixed ...
* @param mixed ...$args
*
* @return self
*/
public function with()
public function with(...$args)
{
return $this->withArgs(func_get_args());
return $this->withArgs($args);
}
/**
* Expected arguments for the expectation passed as an array
*
* @param array $args
* @param array $arguments
* @return self
*/
public function withArgs(array $args)
private function withArgsInArray(array $arguments)
{
if (empty($args)) {
if (empty($arguments)) {
return $this->withNoArgs();
}
$this->_expectedArgs = $args;
$this->_noArgsExpectation = false;
$this->_expectedArgs = $arguments;
return $this;
}
/**
* Expected arguments have to be matched by the given closure.
*
* @param Closure $closure
* @return self
*/
private function withArgsMatchedByClosure(Closure $closure)
{
$this->_expectedArgs = [new MultiArgumentClosure($closure)];
return $this;
}
/**
* Expected arguments for the expectation passed as an array or a closure that matches each passed argument on
* each function call.
*
* @param array|Closure $argsOrClosure
* @return self
*/
public function withArgs($argsOrClosure)
{
if (is_array($argsOrClosure)) {
$this->withArgsInArray($argsOrClosure);
} elseif ($argsOrClosure instanceof Closure) {
$this->withArgsMatchedByClosure($argsOrClosure);
} else {
throw new \InvalidArgumentException(sprintf('Call to %s with an invalid argument (%s), only array and ' .
'closure are allowed', __METHOD__, $argsOrClosure));
}
return $this;
}
@@ -397,8 +470,7 @@ class Expectation implements ExpectationInterface
*/
public function withNoArgs()
{
$this->_noArgsExpectation = true;
$this->_expectedArgs = null;
$this->_expectedArgs = [new NoArgs()];
return $this;
}
@@ -409,20 +481,49 @@ class Expectation implements ExpectationInterface
*/
public function withAnyArgs()
{
$this->_expectedArgs = array();
$this->_expectedArgs = [new AnyArgs()];
return $this;
}
/**
* Expected arguments should partially match the real arguments
*
* @param mixed ...$expectedArgs
* @return self
*/
public function withSomeOfArgs(...$expectedArgs)
{
return $this->withArgs(function (...$args) use ($expectedArgs) {
foreach ($expectedArgs as $expectedArg) {
if (!in_array($expectedArg, $args, true)) {
return false;
}
}
return true;
});
}
/**
* Set a return value, or sequential queue of return values
*
* @param mixed ...$args
* @return self
*/
public function andReturn(...$args)
{
$this->_returnQueue = $args;
return $this;
}
/**
* Set a return value, or sequential queue of return values
*
* @param mixed ...
* @param mixed ...$args
* @return self
*/
public function andReturn()
public function andReturns(...$args)
{
$this->_returnQueue = func_get_args();
return $this;
return call_user_func_array([$this, 'andReturn'], $args);
}
/**
@@ -452,12 +553,34 @@ class Expectation implements ExpectationInterface
* values. The arguments passed to the expected method are passed to the
* closures as parameters.
*
* @param callable ...
* @param callable ...$args
* @return self
*/
public function andReturnUsing()
public function andReturnUsing(...$args)
{
$this->_closureQueue = func_get_args();
$this->_closureQueue = $args;
return $this;
}
/**
* Sets up a closure to return the nth argument from the expected method call
*
* @param int $index
* @return self
*/
public function andReturnArg($index)
{
if (!is_int($index) || $index < 0) {
throw new \InvalidArgumentException("Invalid argument index supplied. Index must be a non-negative integer.");
}
$closure = function (...$args) use ($index) {
if (array_key_exists($index, $args)) {
return $args[$index];
}
throw new \OutOfBoundsException("Cannot return an argument value. No argument exists for the index $index");
};
$this->_closureQueue = [$closure];
return $this;
}
@@ -468,7 +591,7 @@ class Expectation implements ExpectationInterface
*/
public function andReturnUndefined()
{
$this->andReturn(new \Mockery\Undefined);
$this->andReturn(new \Mockery\Undefined());
return $this;
}
@@ -479,16 +602,26 @@ class Expectation implements ExpectationInterface
*/
public function andReturnNull()
{
return $this;
return $this->andReturn(null);
}
public function andReturnFalse()
{
return $this->andReturn(false);
}
public function andReturnTrue()
{
return $this->andReturn(true);
}
/**
* Set Exception class and arguments to that class to be thrown
*
* @param string $exception
* @param string|\Exception $exception
* @param string $message
* @param int $code
* @param Exception $previous
* @param \Exception $previous
* @return self
*/
public function andThrow($exception, $message = '', $code = 0, \Exception $previous = null)
@@ -502,6 +635,11 @@ class Expectation implements ExpectationInterface
return $this;
}
public function andThrows($exception, $message = '', $code = 0, \Exception $previous = null)
{
return $this->andThrow($exception, $message, $code, $previous);
}
/**
* Set Exception classes to be thrown
*
@@ -523,17 +661,34 @@ class Expectation implements ExpectationInterface
* Register values to be set to a public property each time this expectation occurs
*
* @param string $name
* @param mixed $value
* @param array ...$values
* @return self
*/
public function andSet($name, $value)
public function andSet($name, ...$values)
{
$values = func_get_args();
array_shift($values);
$this->_setQueue[$name] = $values;
return $this;
}
/**
* Sets up a closure that will yield each of the provided args
*
* @param mixed ...$args
* @return self
*/
public function andYield(...$args)
{
$this->_closureQueue = [
static function () use ($args) {
foreach ($args as $arg) {
yield $arg;
}
},
];
return $this;
}
/**
* Alias to andSet(). Allows the natural English construct
* - set('foo', 'bar')->andReturn('bar')
@@ -561,6 +716,7 @@ class Expectation implements ExpectationInterface
* Indicates the number of times this expectation should occur
*
* @param int $limit
* @throws \InvalidArgumentException
* @return self
*/
public function times($limit = null)
@@ -568,8 +724,16 @@ class Expectation implements ExpectationInterface
if (is_null($limit)) {
return $this;
}
$this->_countValidators[] = new $this->_countValidatorClass($this, $limit);
$this->_countValidatorClass = 'Mockery\CountValidator\Exact';
if (!is_int($limit)) {
throw new \InvalidArgumentException('The passed Times limit should be an integer value');
}
$this->_countValidators[$this->_countValidatorClass] = new $this->_countValidatorClass($this, $limit);
if ('Mockery\CountValidator\Exact' !== $this->_countValidatorClass) {
$this->_countValidatorClass = 'Mockery\CountValidator\Exact';
unset($this->_countValidators[$this->_countValidatorClass]);
}
return $this;
}
@@ -636,6 +800,19 @@ class Expectation implements ExpectationInterface
return $this->atLeast()->times($minimum)->atMost()->times($maximum);
}
/**
* Set the exception message
*
* @param string $message
* @return $this
*/
public function because($message)
{
$this->_because = $message;
return $this;
}
/**
* Indicates that this expectation must be called in a specific given order
*
@@ -712,7 +889,7 @@ class Expectation implements ExpectationInterface
/**
* Return the parent mock of the expectation
*
* @return \Mockery\MockInterface
* @return \Mockery\LegacyMockInterface|\Mockery\MockInterface
*/
public function getMock()
{
@@ -755,4 +932,9 @@ class Expectation implements ExpectationInterface
{
return $this->_name;
}
public function getExceptionMessage()
{
return $this->_because;
}
}

View File

@@ -14,7 +14,7 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
@@ -22,7 +22,6 @@ namespace Mockery;
class ExpectationDirector
{
/**
* Method name the director is directing
*
@@ -33,7 +32,7 @@ class ExpectationDirector
/**
* Mock object the director is attached to
*
* @var \Mockery\MockInterface
* @var \Mockery\MockInterface|\Mockery\LegacyMockInterface
*/
protected $_mock = null;
@@ -62,9 +61,9 @@ class ExpectationDirector
* Constructor
*
* @param string $name
* @param \Mockery\MockInterface $mock
* @param \Mockery\LegacyMockInterface $mock
*/
public function __construct($name, \Mockery\MockInterface $mock)
public function __construct($name, \Mockery\LegacyMockInterface $mock)
{
$this->_name = $name;
$this->_mock = $mock;
@@ -73,7 +72,7 @@ class ExpectationDirector
/**
* Add a new expectation to the director
*
* @param Mutateme\Expectation $expectation
* @param \Mockery\Expectation $expectation
*/
public function addExpectation(\Mockery\Expectation $expectation)
{
@@ -134,18 +133,24 @@ class ExpectationDirector
*/
public function findExpectation(array $args)
{
$expectation = null;
if (!empty($this->_expectations)) {
return $this->_findExpectationIn($this->_expectations, $args);
} else {
return $this->_findExpectationIn($this->_defaults, $args);
$expectation = $this->_findExpectationIn($this->_expectations, $args);
}
if ($expectation === null && !empty($this->_defaults)) {
$expectation = $this->_findExpectationIn($this->_defaults, $args);
}
return $expectation;
}
/**
* Make the given expectation a default for all others assuming it was
* correctly created last
*
* @param \Mockery\Expectation
* @param \Mockery\Expectation $expectation
*/
public function makeExpectationDefault(\Mockery\Expectation $expectation)
{
@@ -170,7 +175,7 @@ class ExpectationDirector
protected function _findExpectationIn(array $expectations, array $args)
{
foreach ($expectations as $exp) {
if ($exp->matchArgs($args) && $exp->isEligible()) {
if ($exp->isEligible() && $exp->matchArgs($args)) {
return $exp;
}
}
@@ -191,6 +196,16 @@ class ExpectationDirector
return $this->_expectations;
}
/**
* Return all expectations assigned to this director
*
* @return array
*/
public function getDefaultExpectations()
{
return $this->_defaults;
}
/**
* Return the number of expectations assigned to this director.
*
@@ -198,6 +213,14 @@ class ExpectationDirector
*/
public function getExpectationCount()
{
return count($this->getExpectations());
$count = 0;
/** @var Expectation $expectations */
$expectations = $this->getExpectations() ?: $this->getDefaultExpectations();
foreach ($expectations as $expectation) {
if ($expectation->isCallCountConstrained()) {
$count++;
}
}
return $count;
}
}

View File

@@ -15,9 +15,10 @@
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010-2014 Pádraic Brady (http://blog.astrumfutura.com)
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery;
interface ExpectationInterface
@@ -28,12 +29,18 @@ interface ExpectationInterface
public function getOrderNumber();
/**
* @return MockInterface
* @return LegacyMockInterface|MockInterface
*/
public function getMock();
/**
* @param mixed $args
* @return self
*/
public function andReturn();
public function andReturn(...$args);
/**
* @return self
*/
public function andReturns();
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery;
class ExpectsHigherOrderMessage extends HigherOrderMessage
{
public function __construct(MockInterface $mock)
{
parent::__construct($mock, "shouldReceive");
}
/**
* @return \Mockery\Expectation
*/
public function __call($method, $args)
{
$expectation = parent::__call($method, $args);
return $expectation->once();
}
}

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;

View File

@@ -1,24 +1,44 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;
class DefinedTargetClass
class DefinedTargetClass implements TargetClassInterface
{
private $rfc;
private $name;
public function __construct(\ReflectionClass $rfc)
public function __construct(\ReflectionClass $rfc, $alias = null)
{
$this->rfc = $rfc;
$this->name = $alias === null ? $rfc->getName() : $alias;
}
public static function factory($name)
public static function factory($name, $alias = null)
{
return new self(new \ReflectionClass($name));
return new self(new \ReflectionClass($name), $alias);
}
public function getName()
{
return $this->rfc->getName();
return $this->name;
}
public function isAbstract()

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;

View File

@@ -1,9 +1,30 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;
use Mockery\Reflector;
class Method
{
/** @var \ReflectionMethod */
private $method;
public function __construct(\ReflectionMethod $method)
@@ -16,28 +37,21 @@ class Method
return call_user_func_array(array($this->method, $method), $args);
}
/**
* @return Parameter[]
*/
public function getParameters()
{
return array_map(function ($parameter) {
return array_map(function (\ReflectionParameter $parameter) {
return new Parameter($parameter);
}, $this->method->getParameters());
}
/**
* @return string|null
*/
public function getReturnType()
{
if (version_compare(PHP_VERSION, '7.0.0-dev') >= 0 && $this->method->hasReturnType()) {
$returnType = (string) $this->method->getReturnType();
if ('self' === $returnType) {
$returnType = "\\".$this->method->getDeclaringClass()->getName();
}
if (version_compare(PHP_VERSION, '7.1.0-dev') >= 0 && $this->method->getReturnType()->allowsNull()) {
$returnType = '?'.$returnType;
}
return $returnType;
}
return '';
return Reflector::getReturnType($this->method);
}
}

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;
@@ -8,8 +26,6 @@ namespace Mockery\Generator;
*/
class MockConfiguration
{
protected static $mockCounter = 0;
/**
* A class that we'd like to mock
*/
@@ -23,6 +39,13 @@ class MockConfiguration
protected $targetInterfaces = array();
protected $targetInterfaceNames = array();
/**
* A number of traits we'd like to mock, keyed by name to attempt to
* keep unique
*/
protected $targetTraits = array();
protected $targetTraitNames = array();
/**
* An object we'd like our mock to proxy to
*/
@@ -62,14 +85,31 @@ class MockConfiguration
*/
protected $allMethods;
public function __construct(array $targets = array(), array $blackListedMethods = array(), array $whiteListedMethods = array(), $name = null, $instanceMock = false, array $parameterOverrides = array())
{
/**
* If true, overrides original class destructor
*/
protected $mockOriginalDestructor = false;
protected $constantsMap = array();
public function __construct(
array $targets = array(),
array $blackListedMethods = array(),
array $whiteListedMethods = array(),
$name = null,
$instanceMock = false,
array $parameterOverrides = array(),
$mockOriginalDestructor = false,
array $constantsMap = array()
) {
$this->addTargets($targets);
$this->blackListedMethods = $blackListedMethods;
$this->whiteListedMethods = $whiteListedMethods;
$this->name = $name;
$this->instanceMock = $instanceMock;
$this->parameterOverrides = $parameterOverrides;
$this->mockOriginalDestructor = $mockOriginalDestructor;
$this->constantsMap = $constantsMap;
}
/**
@@ -82,13 +122,15 @@ class MockConfiguration
public function getHash()
{
$vars = array(
'targetClassName' => $this->targetClassName,
'targetInterfaceNames' => $this->targetInterfaceNames,
'name' => $this->name,
'blackListedMethods' => $this->blackListedMethods,
'whiteListedMethod' => $this->whiteListedMethods,
'instanceMock' => $this->instanceMock,
'parameterOverrides' => $this->parameterOverrides,
'targetClassName' => $this->targetClassName,
'targetInterfaceNames' => $this->targetInterfaceNames,
'targetTraitNames' => $this->targetTraitNames,
'name' => $this->name,
'blackListedMethods' => $this->blackListedMethods,
'whiteListedMethod' => $this->whiteListedMethods,
'instanceMock' => $this->instanceMock,
'parameterOverrides' => $this->parameterOverrides,
'mockOriginalDestructor' => $this->mockOriginalDestructor
);
return md5(serialize($vars));
@@ -192,6 +234,10 @@ class MockConfiguration
$targets = array_merge($targets, $this->targetInterfaceNames);
}
if ($this->targetTraitNames) {
$targets = array_merge($targets, $this->targetTraitNames);
}
if ($this->targetObject) {
$targets[] = $this->targetObject;
}
@@ -202,7 +248,9 @@ class MockConfiguration
$this->whiteListedMethods,
$className,
$this->instanceMock,
$this->parameterOverrides
$this->parameterOverrides,
$this->mockOriginalDestructor,
$this->constantsMap
);
}
@@ -228,6 +276,11 @@ class MockConfiguration
return $this;
}
if (trait_exists($target)) {
$this->addTargetTraitName($target);
return $this;
}
/**
* Default is to set as class, or interface if class already set
*
@@ -265,7 +318,15 @@ class MockConfiguration
}
if (class_exists($this->targetClassName)) {
$dtc = DefinedTargetClass::factory($this->targetClassName);
$alias = null;
if (strpos($this->targetClassName, '@') !== false) {
$alias = (new MockNameBuilder())
->addPart('anonymous_class')
->addPart(md5($this->targetClassName))
->build();
class_alias($this->targetClassName, $alias);
}
$dtc = DefinedTargetClass::factory($this->targetClassName, $alias);
if ($this->getTargetObject() == false && $dtc->isFinal()) {
throw new \Mockery\Exception(
@@ -279,12 +340,26 @@ class MockConfiguration
$this->targetClass = $dtc;
} else {
$this->targetClass = new UndefinedTargetClass($this->targetClassName);
$this->targetClass = UndefinedTargetClass::factory($this->targetClassName);
}
return $this->targetClass;
}
public function getTargetTraits()
{
if (!empty($this->targetTraits)) {
return $this->targetTraits;
}
foreach ($this->targetTraitNames as $targetTrait) {
$this->targetTraits[] = DefinedTargetClass::factory($targetTrait);
}
$this->targetTraits = array_unique($this->targetTraits); // just in case
return $this->targetTraits;
}
public function getTargetInterfaces()
{
if (!empty($this->targetInterfaces)) {
@@ -293,8 +368,8 @@ class MockConfiguration
foreach ($this->targetInterfaceNames as $targetInterface) {
if (!interface_exists($targetInterface)) {
$this->targetInterfaces[] = new UndefinedTargetClass($targetInterface);
return;
$this->targetInterfaces[] = UndefinedTargetClass::factory($targetInterface);
continue;
}
$dtc = DefinedTargetClass::factory($targetInterface);
@@ -349,24 +424,23 @@ class MockConfiguration
*/
public function generateName()
{
$name = 'Mockery_' . static::$mockCounter++;
$nameBuilder = new MockNameBuilder();
if ($this->getTargetObject()) {
$name .= "_" . str_replace("\\", "_", get_class($this->getTargetObject()));
$className = get_class($this->getTargetObject());
$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);
}
if ($this->getTargetClass()) {
$name .= "_" . str_replace("\\", "_", $this->getTargetClass()->getName());
$className = $this->getTargetClass()->getName();
$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);
}
if ($this->getTargetInterfaces()) {
$name .= array_reduce($this->getTargetInterfaces(), function ($tmpname, $i) {
$tmpname .= '_' . str_replace("\\", "_", $i->getName());
return $tmpname;
}, '');
foreach ($this->getTargetInterfaces() as $targetInterface) {
$nameBuilder->addPart($targetInterface->getName());
}
return $name;
return $nameBuilder->build();
}
public function getShortName()
@@ -407,6 +481,11 @@ class MockConfiguration
return $this->parameterOverrides;
}
public function isMockOriginalDestructor()
{
return $this->mockOriginalDestructor;
}
protected function setTargetClassName($targetClassName)
{
$this->targetClassName = $targetClassName;
@@ -429,6 +508,14 @@ class MockConfiguration
$methods = array_merge($methods, $class->getMethods());
}
foreach ($this->getTargetTraits() as $trait) {
foreach ($trait->getMethods() as $method) {
if ($method->isAbstract()) {
$methods[] = $method;
}
}
}
$names = array();
$methods = array_filter($methods, function ($method) use (&$names) {
if (in_array($method->getName(), $names)) {
@@ -452,9 +539,18 @@ class MockConfiguration
$this->targetInterfaceNames[] = $targetInterface;
}
protected function addTargetTraitName($targetTraitName)
{
$this->targetTraitNames[] = $targetTraitName;
}
protected function setTargetObject($object)
{
$this->targetObject = $object;
}
public function getConstantsMap()
{
return $this->constantsMap;
}
}

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;
@@ -15,7 +33,7 @@ class MockConfigurationBuilder
'__toString',
'__isset',
'__destruct',
'__debugInfo',
'__debugInfo', ## mocking this makes it difficult to debug with xdebug
// below are reserved words in PHP
"__halt_compiler", "abstract", "and", "array", "as",
@@ -32,12 +50,31 @@ class MockConfigurationBuilder
"static", "switch", "throw", "trait", "try",
"unset", "use", "var", "while", "xor"
);
protected $php7SemiReservedKeywords = [
"callable", "class", "trait", "extends", "implements", "static", "abstract", "final",
"public", "protected", "private", "const", "enddeclare", "endfor", "endforeach", "endif",
"endwhile", "and", "global", "goto", "instanceof", "insteadof", "interface", "namespace", "new",
"or", "xor", "try", "use", "var", "exit", "list", "clone", "include", "include_once", "throw",
"array", "print", "echo", "require", "require_once", "return", "else", "elseif", "default",
"break", "continue", "switch", "yield", "function", "if", "endswitch", "finally", "for", "foreach",
"declare", "case", "do", "while", "as", "catch", "die", "self", "parent",
];
protected $whiteListedMethods = array();
protected $instanceMock = false;
protected $parameterOverrides = array();
protected $mockOriginalDestructor = false;
protected $targets = array();
protected $constantsMap = array();
public function __construct()
{
$this->blackListedMethods = array_diff($this->blackListedMethods, $this->php7SemiReservedKeywords);
}
public function addTarget($target)
{
$this->targets[] = $target;
@@ -110,6 +147,17 @@ class MockConfigurationBuilder
$this->parameterOverrides = $overrides;
}
public function setMockOriginalDestructor($mockDestructor)
{
$this->mockOriginalDestructor = $mockDestructor;
return $this;
}
public function setConstantsMap(array $map)
{
$this->constantsMap = $map;
}
public function getMockConfiguration()
{
return new MockConfiguration(
@@ -118,7 +166,9 @@ class MockConfigurationBuilder
$this->whiteListedMethods,
$this->name,
$this->instanceMock,
$this->parameterOverrides
$this->parameterOverrides,
$this->mockOriginalDestructor,
$this->constantsMap
);
}
}

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;

View File

@@ -7,43 +7,40 @@
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/master/LICENSE
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @subpackage UnitTests
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace test\Mockery\Fixtures;
namespace Mockery\Generator;
class MethodWithNullableParameters
class MockNameBuilder
{
public function nonNullablePrimitive(string $a)
protected static $mockCounter = 0;
protected $parts = [];
public function addPart($part)
{
$this->parts[] = $part;
return $this;
}
public function nullablePrimitive(?string $a)
public function build()
{
}
$parts = ['Mockery', static::$mockCounter++];
public function nonNullableSelf(self $a)
{
}
foreach ($this->parts as $part) {
$parts[] = str_replace("\\", "_", $part);
}
public function nullableSelf(?self $a)
{
}
public function nonNullableClass(MethodWithNullableParameters $a)
{
}
public function nullableClass(?MethodWithNullableParameters $a)
{
return implode('_', $parts);
}
}

View File

@@ -1,11 +1,33 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator;
use Mockery\Reflector;
class Parameter
{
private static $parameterCounter;
/** @var int */
private static $parameterCounter = 0;
/** @var \ReflectionParameter */
private $rfp;
public function __construct(\ReflectionParameter $rfp)
@@ -18,84 +40,78 @@ class Parameter
return call_user_func_array(array($this->rfp, $method), $args);
}
/**
* Get the reflection class for the parameter type, if it exists.
*
* This will be null if there was no type, or it was a scalar or a union.
*
* @return \ReflectionClass|null
*
* @deprecated since 1.3.3 and will be removed in 2.0.
*/
public function getClass()
{
return new DefinedTargetClass($this->rfp->getClass());
}
$typeHint = Reflector::getTypeHint($this->rfp, true);
public function getTypeHintAsString()
{
if (method_exists($this->rfp, 'getTypehintText')) {
// Available in HHVM
$typehint = $this->rfp->getTypehintText();
// not exhaustive, but will do for now
if (in_array($typehint, array('int', 'integer', 'float', 'string', 'bool', 'boolean'))) {
return '';
}
return $typehint;
}
if ($this->rfp->isArray()) {
return 'array';
}
/*
* PHP < 5.4.1 has some strange behaviour with a typehint of self and
* subclass signatures, so we risk the regexp instead
*/
if ((version_compare(PHP_VERSION, '5.4.1') >= 0)) {
try {
if ($this->rfp->getClass()) {
return $this->getOptionalSign() . $this->rfp->getClass()->getName();
}
} catch (\ReflectionException $re) {
// noop
}
}
if (version_compare(PHP_VERSION, '7.0.0-dev') >= 0 && $this->rfp->hasType()) {
return $this->getOptionalSign() . $this->rfp->getType();
}
if (preg_match('/^Parameter #[0-9]+ \[ \<(required|optional)\> (?<typehint>\S+ )?.*\$' . $this->rfp->getName() . ' .*\]$/', $this->rfp->__toString(), $typehintMatch)) {
if (!empty($typehintMatch['typehint'])) {
return $typehintMatch['typehint'];
}
}
return '';
}
private function getOptionalSign()
{
if (version_compare(PHP_VERSION, '7.1.0-dev', '>=') && $this->rfp->allowsNull() && !$this->rfp->isVariadic()) {
return '?';
}
return '';
return \class_exists($typeHint) ? DefinedTargetClass::factory($typeHint, false) : null;
}
/**
* Some internal classes have funny looking definitions...
* Get the string representation for the paramater type.
*
* @return string|null
*/
public function getTypeHint()
{
return Reflector::getTypeHint($this->rfp);
}
/**
* Get the string representation for the paramater type.
*
* @return string
*
* @deprecated since 1.3.2 and will be removed in 2.0. Use getTypeHint() instead.
*/
public function getTypeHintAsString()
{
return (string) Reflector::getTypeHint($this->rfp, true);
}
/**
* Get the name of the parameter.
*
* Some internal classes have funny looking definitions!
*
* @return string
*/
public function getName()
{
$name = $this->rfp->getName();
if (!$name || $name == '...') {
$name = 'arg' . static::$parameterCounter++;
$name = 'arg' . self::$parameterCounter++;
}
return $name;
}
/**
* Determine if the parameter is an array.
*
* @return bool
*/
public function isArray()
{
return Reflector::isArray($this->rfp);
}
/**
* Variadics only introduced in 5.6
* Determine if the parameter is variadic.
*
* @return bool
*/
public function isVariadic()
{
return version_compare(PHP_VERSION, '5.6.0') >= 0 && $this->rfp->isVariadic();
return $this->rfp->isVariadic();
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\Method;
use Mockery\Generator\Parameter;
use Mockery\Generator\MockConfiguration;
class AvoidMethodClashPass implements Pass
{
public function apply($code, MockConfiguration $config)
{
$names = array_map(function ($method) {
return $method->getName();
}, $config->getMethodsToMock());
foreach (["allows", "expects"] as $method) {
if (in_array($method, $names)) {
$code = preg_replace(
"#// start method {$method}.*// end method {$method}#ms",
"",
$code
);
$code = str_replace(" implements MockInterface", " implements LegacyMockInterface", $code);
}
}
return $code;
}
}

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator\StringManipulation\Pass;

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator\StringManipulation\Pass;

View File

@@ -1,4 +1,22 @@
<?php
/**
* Mockery
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://github.com/padraic/mockery/blob/master/LICENSE
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to padraic@php.net so we can send you a copy immediately.
*
* @category Mockery
* @package Mockery
* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
*/
namespace Mockery\Generator\StringManipulation\Pass;
@@ -19,24 +37,9 @@ class ClassPass implements Pass
}
$className = ltrim($target->getName(), "\\");
if (!class_exists($className)) {
$targetCode = '<?php ';
if ($target->inNamespace()) {
$targetCode.= 'namespace ' . $target->getNamespaceName(). '; ';
}
$targetCode.= 'class ' . $target->getShortName() . ' {} ';
/*
* We could eval here, but it doesn't play well with the way
* PHPUnit tries to backup global state and the require definition
* loader
*/
$tmpfname = tempnam(sys_get_temp_dir(), "Mockery");
file_put_contents($tmpfname, $targetCode);
require $tmpfname;
\Mockery::registerFileForCleanUp($tmpfname);
\Mockery::declareClass($className);
}
$code = str_replace(

View File

@@ -0,0 +1,33 @@
<?php
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
class ConstantsPass implements Pass
{
public function apply($code, MockConfiguration $config)
{
$cm = $config->getConstantsMap();
if (empty($cm)) {
return $code;
}
if (!isset($cm[$config->getName()])) {
return $code;
}
$cm = $cm[$config->getName()];
$constantsCode = '';
foreach ($cm as $constant => $value) {
$constantsCode .= sprintf("\n const %s = %s;\n", $constant, var_export($value, true));
}
$i = strrpos($code, '}');
$code = substr_replace($code, $constantsCode, $i);
$code .= "}\n";
return $code;
}
}

Some files were not shown because too many files have changed in this diff Show More