Completed
Pull Request — master (#156)
by Kenji
02:15
created

CIPHPUnitTestCase::newController()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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