Completed
Pull Request — master (#161)
by Kenji
04:23 queued 01:58
created

CIPHPUnitTestCase::newLibrary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
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
/**
12
 * @property CIPHPUnitTestRequest    $request
13
 * @property CIPHPUnitTestDouble     $double
14
 * @property CIPHPUnitTestReflection $reflection
15
 */
16
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...
17
{
18
	protected $_error_reporting = -1;
19
20
	/**
21
	 * If you have a route with closure, PHPUnit can't serialize global variables.
22
	 * You would see `Exception: Serialization of 'Closure' is not allowed`.
23
	 *
24
	 * @var array
25
	 */
26
	protected $backupGlobalsBlacklist = ['RTR'];
27
28
	/**
29
	 * @var CI_Controller CodeIgniter instance
30
	 */
31
	protected $CI;
32
	
33
	protected $class_map = [
34
		'request'    => 'CIPHPUnitTestRequest',
35
		'double'     => 'CIPHPUnitTestDouble',
36
		'reflection' => 'CIPHPUnitTestReflection',
37
	];
38
39
	public function setCI(CI_Controller $CI)
40
	{
41
		$this->CI = $CI;
42
	}
43
44
	public function __get($name)
45
	{
46
		if (isset($this->class_map[$name]))
47
		{
48
			$this->$name = new $this->class_map[$name]($this);
49
			return $this->$name;
50
		}
51
52
		throw new LogicException('No such property: ' . $name);
53
	}
54
55
	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...
56
	{
57
		// Fix CLI args, because you may set invalid URI characters
58
		// For example, when you run tests on NetBeans
59
		$_SERVER['argv'] = [
60
			'index.php',
61
		];
62
		$_SERVER['argc'] = 1;
63
		
64
		// Reset current directroy
65
		chdir(FCPATH);
66
	}
67
68
	/**
69
	 * Reset CodeIgniter instance and assign new CodeIgniter instance as $this->CI
70
	 */
71
	public function resetInstance()
72
	{
73
		reset_instance();
74
		CIPHPUnitTest::createCodeIgniterInstance();
75
		$this->CI =& get_instance();
76
	}
77
78
	/**
79
	 * Create a controller instance
80
	 *
81
	 * @param string $classname
82
	 * @return CI_Controller
83
	 */
84
	public function newController($classname)
85
	{
86
		reset_instance();
87
		$controller = new $classname;
88
		$this->CI =& get_instance();
89
		return $controller;
90
	}
91
92
	/**
93
	 * Create a model instance
94
	 *
95
	 * @param string $classname
96
	 * @return CI_Model
97
	 */
98
	public function newModel($classname)
99
	{
100
		$this->resetInstance();
101
		$this->CI->load->model($classname);
102
		return $this->CI->$classname;
103
	}
104
105
	/**
106
	 * Create a library instance
107
	 *
108
	 * @param string $classname
109
	 * @return object
110
	 */
111
	public function newLibrary($classname)
112
	{
113
		$classname = strtolower($classname);
114
		
115
		$this->resetInstance();
116
		$this->CI->load->library($classname);
117
		return $this->CI->$classname;
118
	}
119
120
	protected function tearDown()
121
	{
122
		if (class_exists('MonkeyPatch', false))
123
		{
124
			if (MonkeyPatchManager::isEnabled('FunctionPatcher'))
125
			{
126
				try {
127
					MonkeyPatch::verifyFunctionInvocations();
128
				} catch (Exception $e) {
129
					MonkeyPatch::resetFunctions();
130
					throw $e;
131
				}
132
133
				MonkeyPatch::resetFunctions();
134
			}
135
136
			if (MonkeyPatchManager::isEnabled('ConstantPatcher'))
137
			{
138
				MonkeyPatch::resetConstants();
139
			}
140
141
			if (MonkeyPatchManager::isEnabled('MethodPatcher'))
142
			{
143
				try {
144
					MonkeyPatch::verifyMethodInvocations();
145
				} catch (Exception $e) {
146
					MonkeyPatch::resetMethods();
147
					throw $e;
148
				}
149
150
				MonkeyPatch::resetMethods();
151
			}
152
		}
153
	}
154
155
	/**
156
	 * Request to Controller
157
	 *
158
	 * @param string       $http_method HTTP method
159
	 * @param array|string $argv        array of controller,method,arg|uri
160
	 * @param array        $params      POST parameters/Query string
161
	 */
162
	public function request($http_method, $argv, $params = [])
163
	{
164
		return $this->request->request($http_method, $argv, $params);
165
	}
166
167
	/**
168
	 * Request to Controller using ajax request
169
	 *
170
	 * @param string       $http_method HTTP method
171
	 * @param array|string $argv        array of controller,method,arg|uri
172
	 * @param array        $params      POST parameters/Query string
173
	 */
174
	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...
175
	{
176
		$_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest';
177
		return $this->request($http_method, $argv, $params);
178
	}
179
180
	/**
181
	 * Get Mock Object
182
	 *
183
	 * $email = $this->getMockBuilder('CI_Email')
184
	 *	->setMethods(['send'])
185
	 *	->getMock();
186
	 * $email->method('send')->willReturn(TRUE);
187
	 *
188
	 *  will be
189
	 *
190
	 * $email = $this->getDouble('CI_Email', ['send' => TRUE]);
191
	 *
192
	 * @param  string $classname
193
	 * @param  array  $params             [method_name => return_value]
194
	 * @param  bool   $enable_constructor enable constructor or not
195
	 * @return object PHPUnit mock object
196
	 */
197
	public function getDouble($classname, $params, $enable_constructor = false)
198
	{
199
		return $this->double->getDouble($classname, $params, $enable_constructor);
200
	}
201
202
	/**
203
	 * Verifies that method was called exactly $times times
204
	 *
205
	 * $loader->expects($this->exactly(2))
206
	 * 	->method('view')
207
	 * 	->withConsecutive(
208
	 *		['shop_confirm', $this->anything(), TRUE],
209
	 * 		['shop_tmpl_checkout', $this->anything()]
210
	 * 	);
211
	 *
212
	 *  will be
213
	 *
214
	 * $this->verifyInvokedMultipleTimes(
215
	 * 	$loader,
216
	 * 	'view',
217
	 * 	2,
218
	 * 	[
219
	 * 		['shop_confirm', $this->anything(), TRUE],
220
	 * 		['shop_tmpl_checkout', $this->anything()]
221
	 * 	]
222
	 * );
223
	 *
224
	 * @param object $mock   PHPUnit mock object
225
	 * @param string $method
226
	 * @param int    $times
227
	 * @param array  $params arguments
228
	 */
229
	public function verifyInvokedMultipleTimes($mock, $method, $times, $params = null)
230
	{
231
		$this->double->verifyInvokedMultipleTimes(
232
			$mock, $method, $times, $params
233
		);
234
	}
235
236
	/**
237
	 * Verifies a method was invoked at least once
238
	 *
239
	 * @param object $mock   PHPUnit mock object
240
	 * @param string $method
241
	 * @param array  $params arguments
242
	 */
243
	public function verifyInvoked($mock, $method, $params = null)
244
	{
245
		$this->double->verifyInvoked($mock, $method, $params);
246
	}
247
248
	/**
249
	 * Verifies that method was invoked only once
250
	 *
251
	 * @param object $mock   PHPUnit mock object
252
	 * @param string $method
253
	 * @param array  $params arguments
254
	 */
255
	public function verifyInvokedOnce($mock, $method, $params = null)
256
	{
257
		$this->double->verifyInvokedOnce($mock, $method, $params);
258
	}
259
260
	/**
261
	 * Verifies that method was not called
262
	 *
263
	 * @param object $mock   PHPUnit mock object
264
	 * @param string $method
265
	 * @param array  $params arguments
266
	 */
267
	public function verifyNeverInvoked($mock, $method, $params = null)
268
	{
269
		$this->double->verifyNeverInvoked($mock, $method, $params);
270
	}
271
272
	public function warningOff()
273
	{
274
		$this->_error_reporting = error_reporting(
275
			E_ALL & ~E_WARNING & ~E_NOTICE
276
		);
277
	}
278
279
	public function warningOn()
280
	{
281
		error_reporting($this->_error_reporting);
282
	}
283
284
	/**
285
	 * Asserts HTTP response code
286
	 * 
287
	 * @param int $code
288
	 */
289
	public function assertResponseCode($code)
290
	{
291
		$status = $this->request->getStatus();
292
		$actual = $status['code'];
293
294
		$this->assertSame(
295
			$code,
296
			$actual,
297
			'Status code is not ' . $code . ' but ' . $actual . '.'
298
		);
299
	}
300
301
	/**
302
	 * Asserts HTTP response header
303
	 * 
304
	 * @param string $name  header name
305
	 * @param string $value header value
306
	 */
307
	public function assertResponseHeader($name, $value)
308
	{
309
		$CI =& get_instance();
310
		$actual = $CI->output->get_header($name);
311
312
		if ($actual === null)
313
		{
314
			$this->fail("The '$name' header is not set.\nNote that `assertResponseHeader()` can only assert headers set by `\$this->output->set_header()`");
315
		}
316
317
		$this->assertEquals(
318
			$value,
319
			$actual,
320
			"The '$name' header is not '$value' but '$actual'."
321
		);
322
	}
323
324
	/**
325
	 * Asserts HTTP response cookie
326
	 * 
327
	 * @param string       $name            cookie name
328
	 * @param string|array $value           cookie value|array of cookie params
329
	 * @param bool         $allow_duplicate whether to allow duplicated cookies
330
	 */
331
	public function assertResponseCookie($name, $value, $allow_duplicate = false)
332
	{
333
		$CI =& get_instance();
334
		$cookies = isset($CI->output->_cookies[$name])
335
			? $CI->output->_cookies[$name] : null;
336
337
		if ($cookies === null)
338
		{
339
			$this->fail("The cookie '$name' is not set.\nNote that `assertResponseCookie()` can only assert cookies set by `\$this->input->set_cookie()`");
340
		}
341
342
		$count = count($cookies);
343
		if ($count > 1 && ! $allow_duplicate)
344
		{
345
			$values = [];
346
			foreach ($cookies as $key => $val)
347
			{
348
				$values[] = "'{$val['value']}'";
349
			}
350
			$values = implode(' and ', $values);
351
			$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()`");
352
		}
353
354
		// Get the last cookie
355
		$cookie = $cookies[$count - 1];
356
		if (is_string($value))
357
		{
358
			$this->assertEquals(
359
				$value,
360
				$cookie['value'],
361
				"The cookie '$name' value is not '$value' but '{$cookie['value']}'."
362
			);
363
			return;
364
		}
365
366
		foreach ($value as $key => $val)
367
		{
368
			$this->assertEquals(
369
				$value[$key],
370
				$cookie[$key],
371
				"The cookie '$name' $key is not '{$value[$key]}' but '{$cookie[$key]}'."
372
			);
373
		}
374
	}
375
376
	/**
377
	 * Asserts Redirect
378
	 * 
379
	 * @param string $uri  URI to redirect
380
	 * @param int    $code response code
381
	 */
382
	public function assertRedirect($uri, $code = null)
383
	{
384
		$status = $this->request->getStatus();
385
386
		if ($status['redirect'] === null)
387
		{
388
			$this->fail('redirect() is not called.');
389
		}
390
391
		if (! function_exists('site_url'))
392
		{
393
			$CI =& get_instance();
394
			$CI->load->helper('url');
395
		}
396
397
		if (! preg_match('#^(\w+:)?//#i', $uri))
398
		{
399
			$uri = site_url($uri);
400
		}
401
		$absolute_url = $uri;
402
		$expected = 'Redirect to ' . $absolute_url;
403
404
		$this->assertSame(
405
			$expected,
406
			$status['redirect'],
407
			'URL to redirect is not ' . $expected . ' but ' . $status['redirect'] . '.'
408
		);
409
410
		if ($code !== null)
411
		{
412
			$this->assertSame(
413
				$code,
414
				$status['code'],
415
				'Status code is not ' . $code . ' but ' . $status['code'] . '.'
416
			);
417
		}
418
	}
419
}
420