ernestmarcinko/mockutils

PHPUnit test mocking utility tools

1.0.3 2024-03-28 11:52 UTC

This package is auto-updated.

Last update: 2024-10-28 13:30:12 UTC


README

tests

This package provides ome utility functions for PHPUnit TestCase class (and descendants) via the MockUtils trait:

  • Global Mocks - setGlobalMocks() & unsetGlobalMocks() methods to set global function mocks within a namespace
  • Exception assertion - via expectCatchException() - Method similar to expectException(), but without terminating the test execution

Installation & Inclusion

To install the package:

composer require ernestmarcinko/mockutils --dev

Including in your tests

There are two ways, one is using a trait to extend your TestCase functionality (recommended):

use ErnestMarcinko\MockUtils\MockUtils;
use PHPUnit\Framework\TestCase;

class MyTest extends TestCase {
	use MockUtils;
	...
}

..or use the MockUtilsTestCase class instead of TestCase:

use ErnestMarcinko\MockUtils\MockUtilsTestCase;

class MyTest extends MockUtilsTestCase {
	...
}

MockUtilsTestCase is only an empty class extending TestCase and using MockUtils.

Global Mocks

The trait adds two utility functions to setGlobalMocks() and unsetGlobalMocks() to set and unset global mocks.

setGlobalMocks

protected function setGlobalMocks(array $global_mocks, ?string $namespace): void

Sets global mocks to the given namespace.

Parameters

Example

Say you have a service, which uses curl_exec to get a response from an API. During testing you want to mock it and avoid actual connection to the API and instead test with pre-defined responses.

namespace MyNamespace\MySubNamespace;

class MyClass {
	public function handler() {
		//....
		
		$response = curl_exec($curl); // you want to mock this
		
		//You do something with $response below
	}
}

In the test we need the curl_exec to return 'response' for the mock, to do that:

namespace MyTestNamespace;

class TestMyClass {
	public function testHandler() {
		$this->setGlobalMocks(
			[
				'curl_exec' => 'response'
			], 
			'MyNamespace\\MySubNamespace'
		)
	
		$o = new MyClass();
		$this->assertSame( 'expected output', $o->handler() );
	}
}

It's also possible to define a callable instead of a static response:

namespace MyTestNamespace;

class TestMyClass {
	public function testHandler() {
		$this->setGlobalMocks(
			[
				'curl_exec' => function($curl) {
					return 'response';
				}
			], 
			'MyNamespace\\MySubNamespace'
		)
	
		$o = new MyClass();
		$this->assertSame( 'expected output', $o->handler() );
	}
}

unsetGlobalMocks

protected function unsetGlobalMocks(?array $global_mocks=null): void

Unsets the given global mocks or all global mocks previously defined.

Parameters

Examples

namespace MyTestNamespace;

class TestMyClass {
	public function testHandler() {
		$this->setGlobalMocks(
			[
				'time' => fn()=>time()-3600,
				'json_decode' => array(),
				'strval' => fn($v)=>$v,
			], 
			'MyNamespace\\MySubNamespace'
		)
		$o = new MyClass();
		$this->assertSame( 'expected output 1', $o->handler() );
		
		$this->unsetGlobalMocks(array('time')); // unset just the time mock
		$this->assertSame( 'expected output 2', $o->handler() );
		
		$this->unsetGlobalMocks(); // unset all remaining mocks
		$this->assertSame( 'expected output 3', $o->handler() );
	}
}

Exception Assertion

expectCatchException

Checks if the exception was thrown without execution termination.

protected function expectCatchException(callable $fn, string $throwable, ?string $message = null): void 

Compared to PHPUnit core TestCase::expectException this function will not terminate the test execution. The function to test must be however passed in as a closure, ex.: expectCatchException(fn()=>$o->myMethod(), ...)

Parameters

Return values

The function is void, does not return anything, however:

expectCatchException will trigger TestCase::fail() if no exception was thrown, or any of the criteria was not met.

Example

namespace MyTestNamespace;

class TestMyClass {
	public function testHandler() {
		$this->expectCatchException(function(){
			throw new Exception('hey!');
		}, Exception::class);
		
		// Execution continues
		
		$this->expectCatchException(function(){
			throw new Exception('hey!');
		}, Exception::class, 'hey!');
		
		$o = new MyClass();
		$this->expectCatchException(fn()=>$o->handle(), Exception::class);
		
		$this->expectCatchException(
			fn()=>$o->handle(), 
			Exception::class,
			'Exception message!'
		);
	}
}