commit | author | age
|
2207d6
|
1 |
# Functional Tests |
W |
2 |
|
|
3 |
Now that we've written some acceptance tests, functional tests are almost the same, with one major difference: |
|
4 |
Functional tests don't require a web server. |
|
5 |
|
|
6 |
In simple terms we set the `$_REQUEST`, `$_GET` and `$_POST` variables and then we execute the application from a test. |
|
7 |
This may be valuable, as functional tests are faster and provide detailed stack traces on failures. |
|
8 |
|
|
9 |
Codeception can connect to different PHP frameworks that support functional testing: Symfony2, Laravel5, Yii2, |
|
10 |
Zend Framework and others. You just need to enable the desired module in your functional suite configuration to start. |
|
11 |
|
|
12 |
Modules for all of these frameworks share the same interface, and thus your tests are not bound to any one of them. |
|
13 |
This is a sample functional test: |
|
14 |
|
|
15 |
```php |
|
16 |
<?php |
|
17 |
// LoginCest.php |
|
18 |
|
|
19 |
class LoginCest |
|
20 |
{ |
|
21 |
public function tryLogin (FunctionalTester $I) |
|
22 |
{ |
|
23 |
$I->amOnPage('/'); |
|
24 |
$I->click('Login'); |
|
25 |
$I->fillField('Username', 'Miles'); |
|
26 |
$I->fillField('Password', 'Davis'); |
|
27 |
$I->click('Enter'); |
|
28 |
$I->see('Hello, Miles', 'h1'); |
|
29 |
// $I->seeEmailIsSent(); // only for Symfony2 |
|
30 |
} |
|
31 |
} |
|
32 |
``` |
|
33 |
|
|
34 |
As you see, the syntax is the same for functional and acceptance tests. |
|
35 |
|
|
36 |
## Limitations |
|
37 |
|
|
38 |
Functional tests are usually much faster than acceptance tests. But functional tests are less stable as they run Codeception |
|
39 |
and the application in one environment. If your application was not designed to run in long lived processes (e.g. |
|
40 |
if you use the `exit` operator or global variables), then functional tests are probably not for you. |
|
41 |
|
|
42 |
### Headers, Cookies, Sessions |
|
43 |
|
|
44 |
One of the common issues with functional tests is the use of PHP functions that deal with headers, sessions and cookies. |
|
45 |
As you may already know, the `header` function triggers an error if it is executed after PHP has already output something. |
|
46 |
In functional tests we run the application multiple times, thus we will get lots of irrelevant errors in the result. |
|
47 |
|
|
48 |
### External URL's |
|
49 |
|
|
50 |
Functional tests cannot access external URL's, just URL's within your project. You can use Guzzle to open external URL's. |
|
51 |
|
|
52 |
### Shared Memory |
|
53 |
|
|
54 |
In functional testing, unlike running the application the traditional way, the PHP application does not stop |
|
55 |
after it has finished processing a request. Since all requests are run in one memory container, they are not isolated. |
|
56 |
So **if you see that your tests are mysteriously failing when they shouldn't - try to execute a single test.** |
|
57 |
This will show if the tests were failing because they weren't isolated during the run. |
|
58 |
Keep your memory clean, avoid memory leaks and clean global and static variables. |
|
59 |
|
|
60 |
## Enabling Framework Modules |
|
61 |
|
|
62 |
You have a functional testing suite in the `tests/functional` directory. |
|
63 |
To start, you need to include one of the framework modules in the suite configuration file: `tests/functional.suite.yml`. |
|
64 |
|
|
65 |
### Symfony |
|
66 |
|
|
67 |
To perform Symfony integration you just need to include the Symfony module into your test suite. If you also use Doctrine2, |
|
68 |
don't forget to include it too. To make the Doctrine2 module connect using the `doctrine` service from Symfony, |
|
69 |
you should specify the Symfony module as a dependency for Doctrine2: |
|
70 |
|
|
71 |
```yaml |
|
72 |
# functional.suite.yml |
|
73 |
|
|
74 |
actor: FunctionalTester |
|
75 |
modules: |
|
76 |
enabled: |
|
77 |
- Symfony |
|
78 |
- Doctrine2: |
|
79 |
depends: Symfony # connect to Symfony |
|
80 |
- \Helper\Functional |
|
81 |
``` |
|
82 |
|
|
83 |
By default this module will search for AppKernel in the `app` directory. |
|
84 |
|
|
85 |
The module uses the Symfony Profiler to provide additional information and assertions. |
|
86 |
|
|
87 |
[See the full reference](http://codeception.com/docs/modules/Symfony) |
|
88 |
|
|
89 |
### Laravel5 |
|
90 |
|
|
91 |
The [Laravel5](http://codeception.com/docs/modules/Laravel5) module is included and requires no configuration: |
|
92 |
|
|
93 |
```yaml |
|
94 |
# functional.suite.yml |
|
95 |
|
|
96 |
actor: FunctionalTester |
|
97 |
modules: |
|
98 |
enabled: |
|
99 |
- Laravel5 |
|
100 |
- \Helper\Functional |
|
101 |
``` |
|
102 |
|
|
103 |
### Yii2 |
|
104 |
|
|
105 |
Yii2 tests are included in [Basic](https://github.com/yiisoft/yii2-app-basic) |
|
106 |
and [Advanced](https://github.com/yiisoft/yii2-app-advanced) application templates. Follow the Yii2 guides to start. |
|
107 |
|
|
108 |
### Yii |
|
109 |
|
|
110 |
By itself Yii framework does not have an engine for functional testing. |
|
111 |
So Codeception is the first and the only functional testing framework for Yii. |
|
112 |
To use it with Yii include `Yii1` module into config: |
|
113 |
|
|
114 |
```yaml |
|
115 |
# functional.suite.yml |
|
116 |
|
|
117 |
actor: FunctionalTester |
|
118 |
modules: |
|
119 |
enabled: |
|
120 |
- Yii1 |
|
121 |
- \Helper\Functional |
|
122 |
``` |
|
123 |
|
|
124 |
To avoid the common pitfalls we discussed earlier, Codeception provides basic hooks over the Yii engine. |
|
125 |
Please set them up following [the installation steps in the module reference](http://codeception.com/docs/modules/Yii1). |
|
126 |
|
|
127 |
### Zend Framework 2 |
|
128 |
|
|
129 |
Use [the ZF2 module](http://codeception.com/docs/modules/ZF2) to run functional tests inside Zend Framework 2: |
|
130 |
|
|
131 |
```yaml |
|
132 |
# functional.suite.yml |
|
133 |
|
|
134 |
actor: FunctionalTester |
|
135 |
modules: |
|
136 |
enabled: |
|
137 |
- ZF2 |
|
138 |
- \Helper\Functional |
|
139 |
``` |
|
140 |
|
|
141 |
### Zend Framework 1.x |
|
142 |
|
|
143 |
The module for Zend Framework is highly inspired by the ControllerTestCase class, used for functional testing with PHPUnit. |
|
144 |
It follows similar approaches for bootstrapping and cleaning up. |
|
145 |
To start using Zend Framework in your functional tests, include the `ZF1` module: |
|
146 |
|
|
147 |
```yaml |
|
148 |
# functional.suite.yml |
|
149 |
|
|
150 |
actor: FunctionalTester |
|
151 |
modules: |
|
152 |
enabled: |
|
153 |
- ZF1 |
|
154 |
- \Helper\Functional |
|
155 |
``` |
|
156 |
|
|
157 |
[See the full reference](http://codeception.com/docs/modules/ZF1) |
|
158 |
|
|
159 |
### Phalcon |
|
160 |
|
|
161 |
The `Phalcon` module requires creating a bootstrap file which returns an instance of `\Phalcon\Mvc\Application`. |
|
162 |
To start writing functional tests with Phalcon support you should enable the `Phalcon` module |
|
163 |
and provide the path to this bootstrap file: |
|
164 |
|
|
165 |
```yaml |
|
166 |
# functional.suite.yml |
|
167 |
|
|
168 |
actor: FunctionalTester |
|
169 |
modules: |
|
170 |
enabled: |
|
171 |
- Phalcon: |
|
172 |
bootstrap: 'app/config/bootstrap.php' |
|
173 |
cleanup: true |
|
174 |
savepoints: true |
|
175 |
- \Helper\Functional |
|
176 |
``` |
|
177 |
|
|
178 |
[See the full reference](http://codeception.com/docs/modules/Phalcon) |
|
179 |
|
|
180 |
## Writing Functional Tests |
|
181 |
|
|
182 |
Functional tests are written in the same manner as [Acceptance Tests](http://codeception.com/docs/03-AcceptanceTests) |
|
183 |
with the `PhpBrowser` module enabled. All framework modules and the `PhpBrowser` module share the same methods |
|
184 |
and the same engine. |
|
185 |
|
|
186 |
Therefore we can open a web page with `amOnPage` method: |
|
187 |
|
|
188 |
```php |
|
189 |
<?php |
|
190 |
$I = new FunctionalTester($scenario); |
|
191 |
$I->amOnPage('/login'); |
|
192 |
``` |
|
193 |
|
|
194 |
We can click links to open web pages: |
|
195 |
|
|
196 |
```php |
|
197 |
<?php |
|
198 |
$I->click('Logout'); |
|
199 |
// click link inside .nav element |
|
200 |
$I->click('Logout', '.nav'); |
|
201 |
// click by CSS |
|
202 |
$I->click('a.logout'); |
|
203 |
// click with strict locator |
|
204 |
$I->click(['class' => 'logout']); |
|
205 |
``` |
|
206 |
|
|
207 |
We can submit forms as well: |
|
208 |
|
|
209 |
```php |
|
210 |
<?php |
|
211 |
$I->submitForm('form#login', ['name' => 'john', 'password' => '123456']); |
|
212 |
// alternatively |
|
213 |
$I->fillField('#login input[name=name]', 'john'); |
|
214 |
$I->fillField('#login input[name=password]', '123456'); |
|
215 |
$I->click('Submit', '#login'); |
|
216 |
``` |
|
217 |
|
|
218 |
And do assertions: |
|
219 |
|
|
220 |
```php |
|
221 |
<?php |
|
222 |
$I->see('Welcome, john'); |
|
223 |
$I->see('Logged in successfully', '.notice'); |
|
224 |
$I->seeCurrentUrlEquals('/profile/john'); |
|
225 |
``` |
|
226 |
|
|
227 |
Framework modules also contain additional methods to access framework internals. For instance, Laravel5, Phalcon, |
|
228 |
and Yii2 modules have a `seeRecord` method which uses the ActiveRecord layer to check that a record exists in the database. |
|
229 |
|
|
230 |
Take a look at the complete reference for the module you are using. Most of its methods are common to all modules |
|
231 |
but some of them are unique. |
|
232 |
|
|
233 |
You can also access framework globals inside a test or access the dependency injection container |
|
234 |
inside the `Helper\Functional` class: |
|
235 |
|
|
236 |
```php |
|
237 |
<?php |
|
238 |
namespace Helper; |
|
239 |
|
|
240 |
class Functional extends \Codeception\Module |
|
241 |
{ |
|
242 |
function doSomethingWithMyService() |
|
243 |
{ |
|
244 |
$service = $this->getModule('Symfony')->grabServiceFromContainer('myservice'); |
|
245 |
$service->doSomething(); |
|
246 |
} |
|
247 |
} |
|
248 |
``` |
|
249 |
|
|
250 |
Also check all available *Public Properties* of the used modules to get full access to their data. |
|
251 |
|
|
252 |
## Error Reporting |
|
253 |
|
|
254 |
By default Codeception uses the `E_ALL & ~E_STRICT & ~E_DEPRECATED` error reporting level. |
|
255 |
In functional tests you might want to change this level depending on your framework's error policy. |
|
256 |
The error reporting level can be set in the suite configuration file: |
|
257 |
|
|
258 |
```yaml |
|
259 |
actor: FunctionalTester |
|
260 |
modules: |
|
261 |
enabled: |
|
262 |
- Yii1 |
|
263 |
- \Helper\Functional |
|
264 |
error_level: "E_ALL & ~E_STRICT & ~E_DEPRECATED" |
|
265 |
``` |
|
266 |
|
|
267 |
`error_level` can also be set globally in `codeception.yml` file. |
|
268 |
|
|
269 |
## Conclusion |
|
270 |
|
|
271 |
Functional tests are great if you are using powerful frameworks. By using functional tests you can access |
|
272 |
and manipulate their internal state. This makes your tests shorter and faster. In other cases, |
|
273 |
if you don't use frameworks there is no practical reason to write functional tests. |
|
274 |
If you are using a framework other than the ones listed here, create a module for it and share it with the community. |