Completed
Pull Request — master (#88)
by Kenji
04:15
created

CIPHPUnitTestCase::verifyInvokedOnce()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

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