• Stars
    star
    787
  • Rank 55,543 (Top 2 %)
  • Language
    PHP
  • License
    MIT License
  • Created almost 11 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The most powerful and flexible mocking framework for PHPUnit / Codeception.

AspectMock

AspectMock is not an ordinary PHP mocking framework. With the power of Aspect Oriented programming and the awesome Go-AOP library, AspectMock allows you to stub and mock practically anything in your PHP code!

Documentation | Test Doubles Builder | ClassProxy | InstanceProxy | FuncProxy

Actions Status Latest Stable Version Total Downloads Monthly Downloads StandWithUkraine

Motivation

PHP is a language that was not designed to be testable. Really. How would you fake the time() function to produce the same result for each test call? Is there any way to stub a static method of a class? Can you redefine a class method at runtime? Dynamic languages like Ruby or JavaScript allow us to do this. These features are essential for testing. AspectMock to the rescue!

Thousands of lines of untested code are written everyday in PHP. In most cases, this code is not actually bad, but PHP does not provide capabilities to test it. You may suggest rewriting it from scratch following test driven design practices and use dependency injection wherever possible. Should this be done for stable working code? Well, there are much better ways to waste time.

With AspectMock you can unit-test practically any OOP code. PHP powered with AOP incorporates features of dynamic languages we have long been missing. There is no excuse for not testing your code. You do not have to rewrite it from scratch to make it testable. Just install AspectMock with PHPUnit or Codeception and try to write some tests. It's really, really simple!

Features

  • Create test doubles for static methods.
  • Create test doubles for class methods called anywhere.
  • Redefine methods on the fly.
  • Simple syntax that's easy to remember.

Code Pitch

Allows stubbing and mocking of static methods.

Let's redefine static methods and verify their calls at runtime.

<?php

function testTableName()
{
	$this->assertSame('users', UserModel::tableName());	
	$userModel = test::double('UserModel', ['tableName' => 'my_users']);
	$this->assertSame('my_users', UserModel::tableName());
	$userModel->verifyInvoked('tableName');	
}

Allows replacement of class methods.

Testing code developed with the ActiveRecord pattern. Does the use of the ActiveRecord pattern sound like bad practice? No. But the code below is untestable in classic unit testing.

<?php

class UserService {
    function createUserByName($name) {
    	$user = new User;
    	$user->setName($name);
    	$user->save();
    }
}

Without AspectMock you need to introduce User as an explicit dependency into class UserService to get it tested. But lets leave the code as it is. It works. Nevertheless, we should still test it to avoid regressions.

We don't want the $user->save method to actually get executed, as it will hit the database. Instead we will replace it with a dummy and verify that it gets called by createUserByName:

<?php

function testUserCreate()
{
	$user = test::double('User', ['save' => null]);
	$service = new UserService;
	$service->createUserByName('davert');
	$this->assertSame('davert', $user->getName());
	$user->verifyInvoked('save');
}

Intercept even parent class methods and magic methods

<?php

// User extends ActiveRecord
function testUserCreate()
{
	$AR = test::double('ActiveRecord', ['save' => null]));
	test::double('User', ['findByNameAndEmail' => new User(['name' => 'jon'])])); 
	$user = User::findByNameAndEmail('jon','[email protected]'); // magic method
	$this->assertSame('jon', $user->getName());
	$user->save(['name' => 'miles']); // ActiveRecord->save did not hit database
	$AR->verifyInvoked('save');
	$this->assertSame('miles', $user->getName());
}

Override even standard PHP functions

<?php

namespace demo;
test::func('demo', 'time', 'now');
$this->assertSame('now', time());

Beautifully simple

Only 4 methods are necessary for method call verification and one method to define test doubles:

<?php

function testSimpleStubAndMock()
{
	$user = test::double(new User, ['getName' => 'davert']);
	$this->assertSame('davert', $user->getName());
	$user->verifyInvoked('getName');
	$user->verifyInvokedOnce('getName');
	$user->verifyNeverInvoked('setName');
	$user->verifyInvokedMultipleTimes('setName',1);
}

To check that method setName was called with davert as argument.

<?php
$user->verifyMethodInvoked('setName', ['davert']);

Wow! But how does it work?

No PECL extensions is required. The Go! AOP library does the heavy lifting by patching autoloaded PHP classes on the fly. By introducing pointcuts to every method call, Go! allows intercepting practically any call to a method. AspectMock is a very tiny framework consisting of only 8 files using the power of the Go! AOP Framework. Check out Aspect Oriented Development and the Go! library itself.

Requirements

  • PHP 7.4.
  • Go! AOP 3.0

Installation

1. Add aspect-mock to your composer.json.

{
	"require-dev": {
		"codeception/aspect-mock": "*"
	}
}

2. Install AspectMock with Go! AOP as a dependency.

php composer.phar update

Configuration

Include AspectMock\Kernel class into your tests bootstrap file.

With Composer's Autoloader

<?php

include __DIR__.'/../vendor/autoload.php'; // composer autoload

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'debug' => true,
    'includePaths' => [__DIR__.'/../src']
]);

If your project uses Composer's autoloader, that's all you need to get started.

With Custom Autoloader

If you use a custom autoloader (like in Yii/Yii2 frameworks), you should explicitly point AspectMock to modify it:

<?php

include __DIR__.'/../vendor/autoload.php'; // composer autoload

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'debug' => true,
    'includePaths' => [__DIR__.'/../src']
]);
$kernel->loadFile('YourAutoloader.php'); // path to your autoloader

Load all autoloaders of your project this way, if you do not rely on Composer entirely.

Without Autoloader

If it still doesn't work for you...

Explicitly load all required files before testing:

<?php

include __DIR__.'/../vendor/autoload.php'; // composer autoload

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'debug' => true,
    'includePaths' => [__DIR__.'/../src']
]);
require 'YourAutoloader.php';
$kernel->loadPhpFiles('/../common');

Customization

There are a few options you can customize setting up AspectMock. All them are defined in Go! Framework. They might help If you still didn't get AspectMock running on your project.

  • appDir defines the root of web application which is being tested. All classes outside the root will be replaced with the proxies generated by AspectMock. By default it is a directory in which vendor dir of composer if located. If you don't use Composer or you have custom path to composer's vendor's folder, you should specify appDir
  • cacheDir a dir where updated source PHP files can be stored. If this directory is not set, proxie classes will be built on each run. Otherwise all PHP files used in tests will be updated with aspect injections and stored into cacheDir path.
  • includePaths directories with files that should be enhanced by Go Aop. Should point to your applications source files as well as framework files and any libraries you use..
  • excludePaths a paths in which PHP files should not be affected by aspects. You should exclude your tests files from interception.

Example:

<?php

$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
    'appDir'    => __DIR__ . '/../../',
    'cacheDir'  => '/tmp/myapp',
    'includePaths' => [__DIR__.'/../src']
    'excludePaths' => [__DIR__] // tests dir should be excluded
]);

More configs for different frameworks.

It's pretty important to configure AspectMock properly. Otherwise it may not work as expected or you get side effects. Please make sure you included all files that you need to mock, but your test files as well as testing frameworks are excluded.

Usage in PHPUnit

Use newly created bootstrap in your phpunit.xml configuration. Also disable backupGlobals:

<phpunit bootstrap="bootstrap.php" backupGlobals="false">

Clear the test doubles registry between tests.

<?php

use AspectMock\Test as test;

class UserTest extends \PHPUnit_Framework_TestCase
{
    protected function tearDown()
    {
        test::clean(); // remove all registered test doubles
    }

    public function testDoubleClass()
    {
        $user = test::double('demo\UserModel', ['save' => null]);
        \demo\UserModel::tableName();
        \demo\UserModel::tableName();
        $user->verifyInvokedMultipleTimes('tableName',2);
    }

Usage in Codeception.

Include AspectMock\Kernel into tests/_bootstrap.php. We recommend including a call to test::clean() from your CodeHelper class:

<?php

namespace Codeception\Module;

class CodeHelper extends \Codeception\Module
{
	function _after(\Codeception\TestCase $test)
	{
		\AspectMock\Test::clean();
	}
}

Improvements?

There is guaranteed to be room for improvements. This framework was not designed to do everything you might ever need (see notes below). But if you feel like you require a feature, please submit a Pull Request. It's pretty easy since there's not much code, and the Go! library is very well documented.

Credits

Follow @codeception for updates.

Developed by Michael Bodnarchuk.

License: MIT.

Powered by Go! Aspect-Oriented Framework

More Repositories

1

Codeception

Full-stack testing PHP framework
PHP
4,742
star
2

Stub

Flexible Stub wrapper for PHPUnit's Mock Builder
PHP
292
star
3

phpunit-wrapper

PHPUnit bridge for Codeception
PHP
239
star
4

Specify

BDD style code blocks for PHPUnit / Codeception
PHP
156
star
5

Verify

BDD Assertions for PHPUnit and Codeception
PHP
142
star
6

sample-l4-app

Codeception Laravel Tests
PHP
104
star
7

lib-asserts

Assertion methods used by Codeception core and Asserts module
PHP
93
star
8

module-symfony

Codeception module for testing apps using Symfony framework
PHP
77
star
9

lib-innerbrowser

InnerBrowser
PHP
73
star
10

c3

Remote CodeCoverage for Codeception. Part of Codeception testing framework.
PHP
71
star
11

module-asserts

Codeception module containing various assertions
PHP
70
star
12

robo-paracept

Robo tasks for Codeception tests parallel execution
PHP
56
star
13

module-phpbrowser

PhpBrowser module for Codeception
PHP
53
star
14

module-rest

REST module for Codeception
PHP
49
star
15

phalcon-demo

PHP
43
star
16

module-doctrine2

Doctrine2 module for Codeception
PHP
36
star
17

MockeryModule

Mockery module for Codeception
PHP
33
star
18

module-webdriver

WebDriver module for Codeception
PHP
32
star
19

SeleniumEnv

Docker image with Selenium, Xvfb, Firefox, and Chromium included
Shell
26
star
20

module-db

DB module for Codeception
PHP
23
star
21

codeception.github.com

Codeception Site
HTML
23
star
22

DomainAssert

Domain-specific assertions for PHPUnit and Codeception
PHP
22
star
23

YiiBridge

Wrapper classes required to run Yii functional tests with Codeception
PHP
20
star
24

PhantomJsEnv

Docker image with PhantomJS installed
Shell
20
star
25

base

Base Codeception distribution with minimal set of dependencies (excluding WebDriver and Guzzle)
PHP
18
star
26

AssertThrows

Assert exception handling without stopping a test. For PHPUnit 6+
PHP
18
star
27

module-yii2

Codeception module for Yii2 framework
PHP
16
star
28

module-filesystem

Filesystem module for Codeception
PHP
14
star
29

Notifier

Notification Extension for Codeception
PHP
14
star
30

WordPress-plugin-testing

A source files for WordPress plugin testing tutorial.
PHP
13
star
31

module-cli

Cli module for Codeception
PHP
13
star
32

lib-web

Code shared by module-webdriver and lib-innerbrowser or module-phpbrowser
PHP
12
star
33

codeceptjs-demo

simple demos of CodeceptJS
JavaScript
9
star
34

module-datafactory

DataFactory module for Codeception
PHP
8
star
35

symfony-module-tests

Minimal site containing functional tests for Codeception Symfony module.
PHP
8
star
36

lib-xml

Code used by module-rest and module-soap
PHP
7
star
37

yii2-tests

Sample Yii2 tests
PHP
7
star
38

module-phalcon5

Phalcon 5 module for Codeception
PHP
6
star
39

laravel-module-tests

Tests for Laravel Module
PHP
5
star
40

module-laravel

Modern Laravel module for Codeception
PHP
5
star
41

symfony1module

Module for symfony1.x framework
PHP
5
star
42

module-sequence

Sequence module for Codeception
PHP
5
star
43

module-amqp

AMQP module for Codeception
PHP
3
star
44

module-phalcon4

Phalcon 4 module for Codeception
PHP
3
star
45

docs.pt_BR

Codeception docs pr_BR translation
3
star
46

module-queue

Queue module for Codeception
PHP
3
star
47

module-phalcon

Codeception module for Phalcon framework
PHP
2
star
48

util-robohelpers

Helper methods for Robo files
PHP
2
star
49

module-laravel5

Codeception module for Laravel 5 framework
PHP
2
star
50

module-redis

Redis module for Codeception
PHP
2
star
51

e2cloud

Cloud runner for NodeJS tests
JavaScript
2
star
52

module-memcache

Memcache module for Codeception
PHP
2
star
53

Aerospike-module

Aerospike module for Codeception
PHP
2
star
54

module-zf2

Codeception module for Zend Framework 2 and 3
PHP
2
star
55

module-ftp

FTP module for Codeception
PHP
1
star
56

dbh-module

Dbh module moved out of Codeception repo
1
star
57

module-mongodb

MongoDB module for Codeception
PHP
1
star
58

module-apc

APC module for Codeception
PHP
1
star
59

module-mezzio

Codeception Module for Mezzio framework (formerly known as Zend Expressive)
PHP
1
star
60

module-laminas

Codeception module for Laminas framework
PHP
1
star
61

util-universalframework

Mock framework module used in internal tests
PHP
1
star