Skip to content

Commit

Permalink
Merge pull request #731 from mockery/documentation/issue442
Browse files Browse the repository at this point in the history
Documentation updates
  • Loading branch information
davedevelopment committed May 13, 2017
2 parents c1a3f2e + 825b6c3 commit acd1671
Show file tree
Hide file tree
Showing 36 changed files with 1,710 additions and 693 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

# 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
Expand Down
52 changes: 52 additions & 0 deletions docs/cookbook/big_parent_class.rst
Original file line number Diff line number Diff line change
@@ -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.
144 changes: 144 additions & 0 deletions docs/cookbook/class_constants.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
.. index::
single: Cookbook; Class Constants

Class Constants
===============

When creating a test double for a class, Mockery does not create stubs out of
any class contants 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 contants in Mockery would be possible, it does require
an awful lot of work, for a small number of use cases.

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::mock('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);
3 changes: 3 additions & 0 deletions docs/cookbook/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Cookbook

default_expectations
detecting_mock_objects
not_calling_the_constructor
mocking_hard_dependencies
class_constants
big_parent_class

.. include:: map.rst.inc
3 changes: 3 additions & 0 deletions docs/cookbook/map.rst.inc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
* :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`
63 changes: 63 additions & 0 deletions docs/cookbook/not_calling_the_constructor.rst
Original file line number Diff line number Diff line change
@@ -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 print "Original constructor called."
$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.
1 change: 1 addition & 0 deletions docs/getting_started/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ Getting Started
installation
upgrading
simple_example
quick_reference

.. include:: map.rst.inc
2 changes: 1 addition & 1 deletion docs/getting_started/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Installation
============

Mockery can be installed using Composer or by cloning it from its GitHub
repository. These three options are out orlined below.
repository. These two options are outlined below.

Composer
--------
Expand Down
1 change: 1 addition & 0 deletions docs/getting_started/map.rst.inc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* :doc:`/getting_started/installation`
* :doc:`/getting_started/upgrading`
* :doc:`/getting_started/simple_example`
* :doc:`/getting_started/quick_reference`

0 comments on commit acd1671

Please sign in to comment.