Writing Functional Tests with Codeception

In the introduction to testing article, we took a quick look at functional testing and why you would need to write them. Now, let's actually see how to write them.

What tool to use?

There are primarily two tools used to write functional/acceptance tests in PHP, Codeception and Behat. As you know from the title, we'll use Codeception in this tutorial; too many reasons; the main one is because it's easier to learn.

Acceptance tests? What are those?

You may be thinking now “what is that acceptance testing?”. The answer to this may require a full post on its own (if not more)! However, to give you a quick idea, you can think of it the same as functional testing, but written more specifically for the customer — someone who usually doesn't understand the code.

So it's sometimes thought of as more black-box testing than functional testing. In other words, you don't have much control on the tests as you have with functional testing (like seeing what the database contains after executing some code).

I know this isn't a satisfying answer. And you know what? You should not spend too much time on these jargons — believe me, there's yet more to encounter — and instead learn something more practical to use in your projects.

Installing and configuring Codeception

In a new directory, install Codeception via composer:

composer require codeception/codeception --dev

In vendor/bin, you'll get codecept binary file — which we'll use. To make sure it was installed successfully, run this:

vendor/bin/codecept

Or create an alias and run codecept instead.

To start using Codeception, we need first to bootstrap it. We do that by running:

codecept bootstrap

That will create a bunch of files in a new directory called tests.

For basic uses, you'll need to know these files — acceptance.suite.yml, functional.suite.yml, and unit.suite.yml. Those are used for configuration purposes. You can check out the documentation to see what options are available to you.

In this tutorial, however, we'll only use it to add the web driver that will run our tests.

Let's do this now. Open up functional.suite.yml in your editor and add PhpBrowser as our web driver.

class_name: FunctionalTester
modules:
    enabled:
        # add framework module here
        - PhpBrowser:
            url: http://localhost:8000
        - \Helper\Functional

Note how we specified the url. This is what we'll use to serve our application. We'll do that basically by running a simple php server, using this command:

php -S localhost:8000

Creating our very first functional test

We need first to tell Codeception to generate a new test file in order to write our tests in. To do that, run this:

codecept generate:cept functional Greeting

We gave this command two arguments, the suite and the test name. So this should create a new file named GreetingCept.php in the tests/functional directory.

Open up this file. You should see this:

$I = new FunctionalTester($scenario);
$I->wantTo('perform actions and see result');

The first line is to initialize the tester class, which we'll use to write our tests. The second line is to give a description of what we're testing — it's usually a good practice to write a clean description, as it'll give you a useful test report when you run them.

In this case we're testing to see if a welcoming message appears on the homepage. So, change it to something like this:

$I->wantTo('display a welcoming message on the homepage');

There are many methods we can use on the $I object. The documentation has a full list of those. However, you'll notice that you can guess most of them naturally.

Here's what we should write for our test:

$I->amOnPage('/');
$I->see('Hello, World!');

Pretty easy to read, isn't it?

Now, let's try to run the tests — they'll, of course, fail.

We run tests in Codeception with this command:

codecept run

To run all tests in a specific suite, for example the functional suite:

codecept run functional

This will fail. And it'll show you what causes that error.

Let's make it pass by creating index.php and echoing "Hello, World!".

<?php

// index.php
echo "Hello, World!";

Run it. It should pass.

Passing functional test

Another example

Let's try what we've learned with another slightly more sophisticated example.

We'll create a page with a form containing a text input and a submit button. When the user submits the form with some text, s/he'll get a response with that text but reversed.

First, let's write the test and describe what it should do. Generate a new functional test with this command:

codecept generate:cept functional Reverse

Then, put in this code:

$I = new FunctionalTester($scenario);
$I->wantTo('reverse some string');

$I->amOnPage('/reverse.php');

$I->fillField('text', 'HelloWorld');
$I->click('Reverse');

$I->see('dlroWolleH');

This will fail if you run it. To make it pass, put this code in a new file called reverse.php:

<?php
    if (isset($_POST['text']))
    {
        echo strrev($_POST['text']);
        exit;
    }
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>String Reverse</title>
</head>
<body>
    <form method="post">
        <input type="text" name="text">
        <button type="submit">Reverse</button>
    </form>
</body>
</html>

What about javascript?

Is some cases, you'd need to test something that runs with javascript (like using animations). Using the PHP Browser web driver won't work as expected. For those things we usually use a tool called Selenium.

With selenium the tests will run on an actual browser. The browser will open (you'll see it), and it will run the actions you've written in your tests — like filling in a field and clicking on a button.

Using Selenium is pretty easy. First, download it from this page. Then, run it in your terminal using java, like this:

java -jar selenium-server-standalone-2.52.0.jar role -hub

Finally, change the web driver in functional.suite.yml to use it.

class_name: FunctionalTester
modules:
    enabled:
        - WebDriver:
            url: http://localhost:8000
            browser: firefox
        # add framework module here
        - \Helper\Functional

Now run the tests again and see what happens. Cool, isn't it?

You can check the documentation for more details, but a common test method you'll use is $I->waitForElement('#element', 30). This will allow you to wait for an element before executing any tests on it — which is pretty useful!

Wrap up!

This tutorial was intended to give you a quick overview on using Codeception and to write functional tests with it. Actually, we barely scratched the surface here. Nevertheless, I think you're now able to explore this tool more on your own very easily.

Codeception Testing PHP
Taha Shashtari

About Taha Shashtari

I'm a freelance web developer. Laravel & VueJS are my main tools these days and I love building stuff using them. I write constantly on this blog to share my knowledge and thoughts on things related to web development... Let's be friends on twitter.