Completed
Push — master ( 64aab5...1b53bc )
by Kenji
04:43 queued 01:56
created

CIPHPUnitTestCase::disableStrictErrorCheck()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 22 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Part of ci-phpunit-test
4
 *
5
 * @author     Kenji Suzuki <https://github.com/kenjis>
6
 * @license    MIT License
7
 * @copyright  2015 Kenji Suzuki
8
 * @link       https://github.com/kenjis/ci-phpunit-test
9
 */
10
11
// Support PHPUnit 6.0
12
if (! class_exists('PHPUnit_Framework_TestCase'))
13
{
14
	class_alias('PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase');
15
}
16
17
/**
18
 * @property CIPHPUnitTestRequest    $request
19
 * @property CIPHPUnitTestDouble     $double
20
 * @property CIPHPUnitTestReflection $reflection
21
 */
22
class CIPHPUnitTestCase extends PHPUnit_Framework_TestCase
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
23
{
24
	protected $_error_reporting = -1;
25
26
	/**
27
	 * If you have a route with closure, PHPUnit can't serialize global variables.
28
	 * You would see `Exception: Serialization of 'Closure' is not allowed`.
29
	 *
30
	 * @var array
31
	 */
32
	protected $backupGlobalsBlacklist = ['RTR'];
33
34
	/**
35
	 * Detect warnings and notices in a request output
36
	 *
37
	 * @var bool
38
	 */
39
	protected $strictRequestErrorCheck = true;
40
41
	protected $restoreErrorHandler = false;
42
43
	/**
44
	 * @var CI_Controller CodeIgniter instance
45
	 */
46
	protected $CI;
47
48
	protected $class_map = [
49
		'request'    => 'CIPHPUnitTestRequest',
50
		'double'     => 'CIPHPUnitTestDouble',
51
		'reflection' => 'CIPHPUnitTestReflection',
52
	];
53
54
	public function setCI(CI_Controller $CI)
55
	{
56
		$this->CI = $CI;
57
	}
58
59
	public function __get($name)
60
	{
61
		if (isset($this->class_map[$name]))
62
		{
63
			$this->$name = new $this->class_map[$name]($this);
64
			return $this->$name;
65
		}
66
67
		throw new LogicException('No such property: ' . $name);
68
	}
69
70
	public static function setUpBeforeClass()
0 ignored issues
show
Coding Style introduced by
setUpBeforeClass uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
71
	{
72
		// Fix CLI args, because you may set invalid URI characters
73
		// For example, when you run tests on NetBeans
74
		$_SERVER['argv'] = [
75
			'index.php',
76
		];
77
		$_SERVER['argc'] = 1;
78
79
		// Reset current directroy
80
		chdir(FCPATH);
81
	}
82
83
	/**
84
	 * Reset CodeIgniter instance and assign new CodeIgniter instance as $this->CI
85
	 */
86
	public function resetInstance()
87
	{
88
		reset_instance();
89
		CIPHPUnitTest::createCodeIgniterInstance();
90
		$this->CI =& get_instance();
91
	}
92
93
	protected function tearDown()
94
	{
95
		$this->disableStrictErrorCheck();
96
97
		if (class_exists('MonkeyPatch', false))
98
		{
99
			if (MonkeyPatchManager::isEnabled('FunctionPatcher'))
100
			{
101
				try {
102
					MonkeyPatch::verifyFunctionInvocations();
103
				} catch (Exception $e) {
104
					MonkeyPatch::resetFunctions();
105
					throw $e;
106
				}
107
108
				MonkeyPatch::resetFunctions();
109
			}
110
111
			if (MonkeyPatchManager::isEnabled('ConstantPatcher'))
112
			{
113
				MonkeyPatch::resetConstants();
114
			}
115
116
			if (MonkeyPatchManager::isEnabled('MethodPatcher'))
117
			{
118
				try {
119
					MonkeyPatch::verifyMethodInvocations();
120
				} catch (Exception $e) {
121
					MonkeyPatch::resetMethods();
122
					throw $e;
123
				}
124
125
				MonkeyPatch::resetMethods();
126
			}
127
		}
128
	}
129
130
	/**
131
	 * Request to Controller
132
	 *
133
	 * @param string       $http_method HTTP method
134
	 * @param array|string $argv        array of controller,method,arg|uri
135
	 * @param array        $params      POST parameters/Query string
136
	 */
137
	public function request($http_method, $argv, $params = [])
138
	{
139
		if ($this->strictRequestErrorCheck) {
140
			$this->enableStrictErrorCheck();
141
		}
142
143
		return $this->request->request($http_method, $argv, $params);
144
	}
145
146
	/**
147
	 * Disable strict error check
148
	 */
149
	public function disableStrictErrorCheck()
150
	{
151
		if ($this->restoreErrorHandler) {
152
			restore_error_handler();
153
			$this->restoreErrorHandler = false;
154
		}
155
	}
156
157
	/**
158
	 * Enable strict error check
159
	 */
160
	public function enableStrictErrorCheck()
161
	{
162
		if ($this->restoreErrorHandler) {
163
			throw new LogicException('Already strict error check mode');
164
		}
165
166
		set_error_handler(
167
			function ($errno, $errstr, $errfile, $errline) {
168
				throw new RuntimeException($errstr . ' on line ' . $errline . ' in file ' . $errfile);
169
			}
170
		);
171
172
		$this->restoreErrorHandler = true;
173
	}
174
175
	/**
176
	 * Request to Controller using ajax request
177
	 *
178
	 * @param string       $http_method HTTP method
179
	 * @param array|string $argv        array of controller,method,arg|uri
180
	 * @param array        $params      POST parameters/Query string
181
	 */
182
	public function ajaxRequest($http_method, $argv, $params = [])
0 ignored issues
show
Coding Style introduced by
ajaxRequest uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
183
	{
184
		$_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
185
		return $this->request($http_method, $argv, $params);
186
	}
187
188
	/**
189
	 * Get Mock Object
190
	 *
191
	 * $email = $this->getMockBuilder('CI_Email')
192
	 *	->setMethods(['send'])
193
	 *	->getMock();
194
	 * $email->method('send')->willReturn(TRUE);
195
	 *
196
	 *  will be
197
	 *
198
	 * $email = $this->getDouble('CI_Email', ['send' => TRUE]);
199
	 *
200
	 * @param  string $classname
201
	 * @param  array  $params             [method_name => return_value]
202
	 * @param  bool   $enable_constructor enable constructor or not
203
	 * @return object PHPUnit mock object
204
	 */
205
	public function getDouble($classname, $params, $enable_constructor = false)
206
	{
207
		return $this->double->getDouble($classname, $params, $enable_constructor);
208
	}
209
210
	/**
211
	 * Verifies that method was called exactly $times times
212
	 *
213
	 * $loader->expects($this->exactly(2))
214
	 * 	->method('view')
215
	 * 	->withConsecutive(
216
	 *		['shop_confirm', $this->anything(), TRUE],
217
	 * 		['shop_tmpl_checkout', $this->anything()]
218
	 * 	);
219
	 *
220
	 *  will be
221
	 *
222
	 * $this->verifyInvokedMultipleTimes(
223
	 * 	$loader,
224
	 * 	'view',
225
	 * 	2,
226
	 * 	[
227
	 * 		['shop_confirm', $this->anything(), TRUE],
228
	 * 		['shop_tmpl_checkout', $this->anything()]
229
	 * 	]
230
	 * );
231
	 *
232
	 * @param object $mock   PHPUnit mock object
233
	 * @param string $method
234
	 * @param int    $times
235
	 * @param array  $params arguments
236
	 */
237
	public function verifyInvokedMultipleTimes($mock, $method, $times, $params = null)
238
	{
239
		$this->double->verifyInvokedMultipleTimes(
240
			$mock, $method, $times, $params
241
		);
242
	}
243
244
	/**
245
	 * Verifies a method was invoked at least once
246
	 *
247
	 * @param object $mock   PHPUnit mock object
248
	 * @param string $method
249
	 * @param array  $params arguments
250
	 */
251
	public function verifyInvoked($mock, $method, $params = null)
252
	{
253
		$this->double->verifyInvoked($mock, $method, $params);
254
	}
255
256
	/**
257
	 * Verifies that method was invoked only once
258
	 *
259
	 * @param object $mock   PHPUnit mock object
260
	 * @param string $method
261
	 * @param array  $params arguments
262
	 */
263
	public function verifyInvokedOnce($mock, $method, $params = null)
264
	{
265
		$this->double->verifyInvokedOnce($mock, $method, $params);
266
	}
267
268
	/**
269
	 * Verifies that method was not called
270
	 *
271
	 * @param object $mock   PHPUnit mock object
272
	 * @param string $method
273
	 * @param array  $params arguments
274
	 */
275
	public function verifyNeverInvoked($mock, $method, $params = null)
276
	{
277
		$this->double->verifyNeverInvoked($mock, $method, $params);
278
	}
279
280
	public function warningOff()
281
	{
282
		$this->_error_reporting = error_reporting(
283
			E_ALL & ~E_WARNING & ~E_NOTICE
284
		);
285
	}
286
287
	public function warningOn()
288
	{
289
		error_reporting($this->_error_reporting);
290
	}
291
292
	/**
293
	 * Asserts HTTP response code
294
	 *
295
	 * @param int $code
296
	 */
297
	public function assertResponseCode($code)
298
	{
299
		$status = $this->request->getStatus();
300
		$actual = $status['code'];
301
302
		$this->assertSame(
303
			$code,
304
			$actual,
305
			'Status code is not ' . $code . ' but ' . $actual . '.'
306
		);
307
	}
308
309
	/**
310
	 * Asserts HTTP response header
311
	 *
312
	 * @param string $name  header name
313
	 * @param string $value header value
314
	 */
315
	public function assertResponseHeader($name, $value)
316
	{
317
		$CI =& get_instance();
318
		$actual = $CI->output->get_header($name);
319
320
		if ($actual === null)
321
		{
322
			$this->fail("The '$name' header is not set.\nNote that `assertResponseHeader()` can only assert headers set by `\$this->output->set_header()`");
323
		}
324
325
		$this->assertEquals(
326
			$value,
327
			$actual,
328
			"The '$name' header is not '$value' but '$actual'."
329
		);
330
	}
331
332
	/**
333
	 * Asserts HTTP response cookie
334
	 *
335
	 * @param string       $name            cookie name
336
	 * @param string|array $value           cookie value|array of cookie params
337
	 * @param bool         $allow_duplicate whether to allow duplicated cookies
338
	 */
339
	public function assertResponseCookie($name, $value, $allow_duplicate = false)
340
	{
341
		$CI =& get_instance();
342
		$cookies = isset($CI->output->_cookies[$name])
343
			? $CI->output->_cookies[$name] : null;
344
345
		if ($cookies === null)
346
		{
347
			$this->fail("The cookie '$name' is not set.\nNote that `assertResponseCookie()` can only assert cookies set by `\$this->input->set_cookie()`");
348
		}
349
350
		$count = count($cookies);
351
		if ($count > 1 && ! $allow_duplicate)
352
		{
353
			$values = [];
354
			foreach ($cookies as $key => $val)
355
			{
356
				$values[] = "'{$val['value']}'";
357
			}
358
			$values = implode(' and ', $values);
359
			$this->fail("You have more than one cookie '$name'. The values are $values.\nIf it is okay, please set `true` as the 3rd argument of `assertResponseCookie()`");
360
		}
361
362
		// Get the last cookie
363
		$cookie = $cookies[$count - 1];
364
		if (is_string($value))
365
		{
366
			$this->assertEquals(
367
				$value,
368
				$cookie['value'],
369
				"The cookie '$name' value is not '$value' but '{$cookie['value']}'."
370
			);
371
			return;
372
		}
373
374
		// In case of $this->anything()
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
375
		if (
376
			$value instanceof PHPUnit_Framework_Constraint_IsAnything
0 ignored issues
show
Bug introduced by
The class PHPUnit_Framework_Constraint_IsAnything does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
377
			|| $value instanceof PHPUnit\Framework\Constraint\IsAnything
0 ignored issues
show
Bug introduced by
The class PHPUnit\Framework\Constraint\IsAnything does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
378
		)
379
		{
380
			$this->assertTrue(true);
381
			return;
382
		}
383
384
		foreach ($value as $key => $val)
385
		{
386
			$this->assertEquals(
387
				$value[$key],
388
				$cookie[$key],
389
				"The cookie '$name' $key is not '{$value[$key]}' but '{$cookie[$key]}'."
390
			);
391
		}
392
	}
393
394
	/**
395
	 * Asserts Redirect
396
	 *
397
	 * @param string $uri  URI to redirect
398
	 * @param int    $code response code
399
	 */
400
	public function assertRedirect($uri, $code = null)
401
	{
402
		$status = $this->request->getStatus();
403
404
		if ($status['redirect'] === null)
405
		{
406
			$this->fail('redirect() is not called.');
407
		}
408
409
		if (! function_exists('site_url'))
410
		{
411
			$CI =& get_instance();
412
			$CI->load->helper('url');
413
		}
414
415
		if (! preg_match('#^(\w+:)?//#i', $uri))
416
		{
417
			$uri = site_url($uri);
418
		}
419
		$absolute_url = $uri;
420
		$expected = 'Redirect to ' . $absolute_url;
421
422
		$this->assertSame(
423
			$expected,
424
			$status['redirect'],
425
			'URL to redirect is not ' . $expected . ' but ' . $status['redirect'] . '.'
426
		);
427
428
		if ($code !== null)
429
		{
430
			$this->assertSame(
431
				$code,
432
				$status['code'],
433
				'Status code is not ' . $code . ' but ' . $status['code'] . '.'
434
			);
435
		}
436
	}
437
}
438