Completed
Pull Request — master (#232)
by
unknown
09:30
created

CIPHPUnitTestCase   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 428
Duplicated Lines 1.87 %

Coupling/Cohesion

Components 4
Dependencies 4

Importance

Changes 0
Metric Value
dl 8
loc 428
rs 8.3396
c 0
b 0
f 0
wmc 44
lcom 4
cbo 4

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setCI() 0 4 1
A __get() 0 10 2
A setUpBeforeClass() 0 12 1
A resetInstance() 0 6 1
A newController() 0 7 1
C tearDown() 0 34 7
A request() 0 4 1
A ajaxRequest() 0 5 1
A getDouble() 0 4 1
A verifyInvokedMultipleTimes() 0 6 1
A verifyInvoked() 0 4 1
A verifyInvokedOnce() 0 4 1
A verifyNeverInvoked() 0 4 1
A warningOff() 0 6 1
A warningOn() 0 4 1
A assertResponseCode() 0 11 1
A assertResponseHeader() 0 16 2
C assertResponseCookie() 0 54 10
B assertRedirect() 0 37 5
A newModel() 4 13 2
A newLibrary() 4 14 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CIPHPUnitTestCase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CIPHPUnitTestCase, and based on these observations, apply Extract Interface, too.

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
	 * @var CI_Controller CodeIgniter instance
36
	 */
37
	protected $CI;
38
39
	protected $class_map = [
40
		'request'    => 'CIPHPUnitTestRequest',
41
		'double'     => 'CIPHPUnitTestDouble',
42
		'reflection' => 'CIPHPUnitTestReflection',
43
	];
44
45
	public function setCI(CI_Controller $CI)
46
	{
47
		$this->CI = $CI;
48
	}
49
50
	public function __get($name)
51
	{
52
		if (isset($this->class_map[$name]))
53
		{
54
			$this->$name = new $this->class_map[$name]($this);
55
			return $this->$name;
56
		}
57
58
		throw new LogicException('No such property: ' . $name);
59
	}
60
61
	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...
62
	{
63
		// Fix CLI args, because you may set invalid URI characters
64
		// For example, when you run tests on NetBeans
65
		$_SERVER['argv'] = [
66
			'index.php',
67
		];
68
		$_SERVER['argc'] = 1;
69
70
		// Reset current directroy
71
		chdir(FCPATH);
72
	}
73
74
	/**
75
	 * Reset CodeIgniter instance and assign new CodeIgniter instance as $this->CI
76
	 */
77
	public function resetInstance()
78
	{
79
		reset_instance();
80
		CIPHPUnitTest::createCodeIgniterInstance();
81
		$this->CI =& get_instance();
82
	}
83
84
	/**
85
	 * Create a controller instance
86
	 *
87
	 * @param string $classname
88
	 * @return CI_Controller
89
	 */
90
	public function newController($classname)
91
	{
92
		reset_instance();
93
		$controller = new $classname;
94
		$this->CI =& get_instance();
95
		return $controller;
96
	}
97
98
	/**
99
	 * Create a model instance
100
	 *
101
	 * @param string $classname
102
	 * @return CI_Model
103
	 */
104
	public function newModel($classname)
105
	{
106
		$this->resetInstance();
107
		$this->CI->load->model($classname);
108
109
		// Is the model in a sub-folder?
110 View Code Duplication
		if (($last_slash = strrpos($classname, '/')) !== FALSE)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
		{
112
			$classname = substr($classname, ++$last_slash);
113
		}
114
115
		return $this->CI->$classname;
116
	}
117
118
	/**
119
	 * Create a library instance
120
	 *
121
	 * @param string $classname
122
	 * @param array  $args
123
	 * @return object
124
	 */
125
	public function newLibrary($classname, $args = null)
126
	{
127
		$this->resetInstance();
128
		$this->CI->load->library($classname, $args);
129
130
		// Is the library in a sub-folder?
131 View Code Duplication
		if (($last_slash = strrpos($classname, '/')) !== FALSE)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
		{
133
			$classname = substr($classname, ++$last_slash);
134
		}
135
		$classname = strtolower($classname);
136
137
		return $this->CI->$classname;
138
	}
139
140
	protected function tearDown()
141
	{
142
		if (class_exists('MonkeyPatch', false))
143
		{
144
			if (MonkeyPatchManager::isEnabled('FunctionPatcher'))
145
			{
146
				try {
147
					MonkeyPatch::verifyFunctionInvocations();
148
				} catch (Exception $e) {
149
					MonkeyPatch::resetFunctions();
150
					throw $e;
151
				}
152
153
				MonkeyPatch::resetFunctions();
154
			}
155
156
			if (MonkeyPatchManager::isEnabled('ConstantPatcher'))
157
			{
158
				MonkeyPatch::resetConstants();
159
			}
160
161
			if (MonkeyPatchManager::isEnabled('MethodPatcher'))
162
			{
163
				try {
164
					MonkeyPatch::verifyMethodInvocations();
165
				} catch (Exception $e) {
166
					MonkeyPatch::resetMethods();
167
					throw $e;
168
				}
169
170
				MonkeyPatch::resetMethods();
171
			}
172
		}
173
	}
174
175
	/**
176
	 * Request to Controller
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 request($http_method, $argv, $params = [])
183
	{
184
		return $this->request->request($http_method, $argv, $params);
185
	}
186
187
	/**
188
	 * Request to Controller using ajax request
189
	 *
190
	 * @param string       $http_method HTTP method
191
	 * @param array|string $argv        array of controller,method,arg|uri
192
	 * @param array        $params      POST parameters/Query string
193
	 */
194
	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...
195
	{
196
		$_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
197
		return $this->request($http_method, $argv, $params);
198
	}
199
200
	/**
201
	 * Get Mock Object
202
	 *
203
	 * $email = $this->getMockBuilder('CI_Email')
204
	 *	->setMethods(['send'])
205
	 *	->getMock();
206
	 * $email->method('send')->willReturn(TRUE);
207
	 *
208
	 *  will be
209
	 *
210
	 * $email = $this->getDouble('CI_Email', ['send' => TRUE]);
211
	 *
212
	 * @param  string $classname
213
	 * @param  array  $params             [method_name => return_value]
214
	 * @param  bool   $enable_constructor enable constructor or not
215
	 * @return object PHPUnit mock object
216
	 */
217
	public function getDouble($classname, $params, $enable_constructor = false)
218
	{
219
		return $this->double->getDouble($classname, $params, $enable_constructor);
220
	}
221
222
	/**
223
	 * Verifies that method was called exactly $times times
224
	 *
225
	 * $loader->expects($this->exactly(2))
226
	 * 	->method('view')
227
	 * 	->withConsecutive(
228
	 *		['shop_confirm', $this->anything(), TRUE],
229
	 * 		['shop_tmpl_checkout', $this->anything()]
230
	 * 	);
231
	 *
232
	 *  will be
233
	 *
234
	 * $this->verifyInvokedMultipleTimes(
235
	 * 	$loader,
236
	 * 	'view',
237
	 * 	2,
238
	 * 	[
239
	 * 		['shop_confirm', $this->anything(), TRUE],
240
	 * 		['shop_tmpl_checkout', $this->anything()]
241
	 * 	]
242
	 * );
243
	 *
244
	 * @param object $mock   PHPUnit mock object
245
	 * @param string $method
246
	 * @param int    $times
247
	 * @param array  $params arguments
248
	 */
249
	public function verifyInvokedMultipleTimes($mock, $method, $times, $params = null)
250
	{
251
		$this->double->verifyInvokedMultipleTimes(
252
			$mock, $method, $times, $params
253
		);
254
	}
255
256
	/**
257
	 * Verifies a method was invoked at least once
258
	 *
259
	 * @param object $mock   PHPUnit mock object
260
	 * @param string $method
261
	 * @param array  $params arguments
262
	 */
263
	public function verifyInvoked($mock, $method, $params = null)
264
	{
265
		$this->double->verifyInvoked($mock, $method, $params);
266
	}
267
268
	/**
269
	 * Verifies that method was invoked only once
270
	 *
271
	 * @param object $mock   PHPUnit mock object
272
	 * @param string $method
273
	 * @param array  $params arguments
274
	 */
275
	public function verifyInvokedOnce($mock, $method, $params = null)
276
	{
277
		$this->double->verifyInvokedOnce($mock, $method, $params);
278
	}
279
280
	/**
281
	 * Verifies that method was not called
282
	 *
283
	 * @param object $mock   PHPUnit mock object
284
	 * @param string $method
285
	 * @param array  $params arguments
286
	 */
287
	public function verifyNeverInvoked($mock, $method, $params = null)
288
	{
289
		$this->double->verifyNeverInvoked($mock, $method, $params);
290
	}
291
292
	public function warningOff()
293
	{
294
		$this->_error_reporting = error_reporting(
295
			E_ALL & ~E_WARNING & ~E_NOTICE
296
		);
297
	}
298
299
	public function warningOn()
300
	{
301
		error_reporting($this->_error_reporting);
302
	}
303
304
	/**
305
	 * Asserts HTTP response code
306
	 *
307
	 * @param int $code
308
	 */
309
	public function assertResponseCode($code)
310
	{
311
		$status = $this->request->getStatus();
312
		$actual = $status['code'];
313
314
		$this->assertSame(
315
			$code,
316
			$actual,
317
			'Status code is not ' . $code . ' but ' . $actual . '.'
318
		);
319
	}
320
321
	/**
322
	 * Asserts HTTP response header
323
	 *
324
	 * @param string $name  header name
325
	 * @param string $value header value
326
	 */
327
	public function assertResponseHeader($name, $value)
328
	{
329
		$CI =& get_instance();
330
		$actual = $CI->output->get_header($name);
331
332
		if ($actual === null)
333
		{
334
			$this->fail("The '$name' header is not set.\nNote that `assertResponseHeader()` can only assert headers set by `\$this->output->set_header()`");
335
		}
336
337
		$this->assertEquals(
338
			$value,
339
			$actual,
340
			"The '$name' header is not '$value' but '$actual'."
341
		);
342
	}
343
344
	/**
345
	 * Asserts HTTP response cookie
346
	 *
347
	 * @param string       $name            cookie name
348
	 * @param string|array $value           cookie value|array of cookie params
349
	 * @param bool         $allow_duplicate whether to allow duplicated cookies
350
	 */
351
	public function assertResponseCookie($name, $value, $allow_duplicate = false)
352
	{
353
		$CI =& get_instance();
354
		$cookies = isset($CI->output->_cookies[$name])
355
			? $CI->output->_cookies[$name] : null;
356
357
		if ($cookies === null)
358
		{
359
			$this->fail("The cookie '$name' is not set.\nNote that `assertResponseCookie()` can only assert cookies set by `\$this->input->set_cookie()`");
360
		}
361
362
		$count = count($cookies);
363
		if ($count > 1 && ! $allow_duplicate)
364
		{
365
			$values = [];
366
			foreach ($cookies as $key => $val)
367
			{
368
				$values[] = "'{$val['value']}'";
369
			}
370
			$values = implode(' and ', $values);
371
			$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()`");
372
		}
373
374
		// Get the last cookie
375
		$cookie = $cookies[$count - 1];
376
		if (is_string($value))
377
		{
378
			$this->assertEquals(
379
				$value,
380
				$cookie['value'],
381
				"The cookie '$name' value is not '$value' but '{$cookie['value']}'."
382
			);
383
			return;
384
		}
385
386
		// 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...
387
		if (
388
			$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...
389
			|| $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...
390
		)
391
		{
392
			$this->assertTrue(true);
393
			return;
394
		}
395
396
		foreach ($value as $key => $val)
397
		{
398
			$this->assertEquals(
399
				$value[$key],
400
				$cookie[$key],
401
				"The cookie '$name' $key is not '{$value[$key]}' but '{$cookie[$key]}'."
402
			);
403
		}
404
	}
405
406
	/**
407
	 * Asserts Redirect
408
	 *
409
	 * @param string $uri  URI to redirect
410
	 * @param int    $code response code
411
	 */
412
	public function assertRedirect($uri, $code = null)
413
	{
414
		$status = $this->request->getStatus();
415
416
		if ($status['redirect'] === null)
417
		{
418
			$this->fail('redirect() is not called.');
419
		}
420
421
		if (! function_exists('site_url'))
422
		{
423
			$CI =& get_instance();
424
			$CI->load->helper('url');
425
		}
426
427
		if (! preg_match('#^(\w+:)?//#i', $uri))
428
		{
429
			$uri = site_url($uri);
430
		}
431
		$absolute_url = $uri;
432
		$expected = 'Redirect to ' . $absolute_url;
433
434
		$this->assertSame(
435
			$expected,
436
			$status['redirect'],
437
			'URL to redirect is not ' . $expected . ' but ' . $status['redirect'] . '.'
438
		);
439
440
		if ($code !== null)
441
		{
442
			$this->assertSame(
443
				$code,
444
				$status['code'],
445
				'Status code is not ' . $code . ' but ' . $status['code'] . '.'
446
			);
447
		}
448
	}
449
}
450