Creating User Data From Behat Background - Part 1


In this two part video we will write the code behind our Behat User feature background step. More specifically, we will cover the following Background step:

  Background:
    Given there are Users with the following details:
    | uid | username | email          | password |
    | u1  | peter    | peter@test.com | testpass |
    | u2  | john     | john@test.org  | johnpass |

We will cover this in quite some detail. Once you understand this step, the remaining database table steps in the Background phase are very similar.

As mentioned already in this series, Behat is quite a slow starter. There's a heck of a lot of boilerplate code to write for your first few features, which thankfully becomes quite easy to copy / paste as your project grows. Not to mention, as your confidence and familiarity with Behat increases, writing new features and step definitions becomes second nature.

FOS User Bundle Setup

I'm not going to go into great depths about setting up FOSUserBundle. If any of this is new to you, or you are unsure about FOSUserBundle in general then be sure to check out my Getting Started with FOSUserBundle tutorial series.

Whilst we covered the composer dependencies for this project in an earlier video, we didn't enable or configure any of the dependencies at that point. Let's fix that now.

// app/AppKernel.php

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Symfony\Bundle\MonologBundle\MonologBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),

            // our third party dependencies
            new FOS\RestBundle\FOSRestBundle(),
            new Nelmio\CorsBundle\NelmioCorsBundle(),
            new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
            new JMS\SerializerBundle\JMSSerializerBundle(),
            new FOS\UserBundle\FOSUserBundle(),

            // not using these currently, but will enable shortly
//            new Csa\Bundle\GuzzleBundle\CsaGuzzleBundle(),
//            new Oneup\FlysystemBundle\OneupFlysystemBundle(),
//            new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(),

            new AppBundle\AppBundle(),
        ];

        if (in_array($this->getEnvironment(), ['dev', 'test', 'acceptance'], true)) {
            $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
            $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
            $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
        }

        return $bundles;
    }

    // etc

We'll need to add in some new parameters, so be sure to update your parameters.yml.dist, as well as your parameters.yml files:

# app/config/parameters.yml
parameters:
    # standard symfony parameters here have been removed

    # nelmio cors
    cors_allow_origin: 'http://symfony-rest-example.dev'

    # nelmio api docs
    api_name: 'Symfony 3 REST Example'
    api_description: 'Some clever description goes here'%

And we will need to add the expected config to our config.yml file:

# app/config/config.yml

# standard Symfony config removed for brevity

# Nelmio CORS
nelmio_cors:
    defaults:
        allow_origin:  ["%cors_allow_origin%"]
        allow_methods: ["POST", "PUT", "GET", "DELETE", "OPTIONS"]
        allow_headers: ["content-type", "authorization"]
        max_age:       3600
    paths:
        '^/': ~

# Nelmio API Doc
nelmio_api_doc:
    sandbox:
        accept_type:        "application/json"
        body_format:
            formats:        [ "json" ]
            default_format: "json"
        request_format:
            formats:
                json:       "application/json"

# FOS User Bundle
fos_user:
    db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
    firewall_name: main
    user_class: AppBundle\Entity\User

# FOS REST Bundle
fos_rest:
    body_listener: true
    format_listener:  true
    param_fetcher_listener: true
    view:
        view_response_listener: 'force'
        exception_wrapper_handler:  null
        formats:
            jsonp: true
            json: true
            xml: false
            rss: false
        mime_types:
            json: ['application/json', 'application/x-json']
            jpg: 'image/jpeg'
            png: 'image/png'
        jsonp_handler: ~
    routing_loader:
        default_format:  json
        include_format:  false
    format_listener:
        rules:
            - { path: ^/, priorities: [ json, jsonp ], fallback_format: ~, prefer_extension: true }
    exception:
        enabled: true

# JMS Serializer
jms_serializer:
    metadata:
        directories:
            FOSUB:
                namespace_prefix: FOS\UserBundle
                path: %kernel.root_dir%/serializer/FOSUB

We will add the rest of the config as we get to it in future videos, but for now, these are the 'core' pieces we will need. Largely this is copy and paste from the various documentation files for each of the projects.

If you haven't used FOSRESTBundle before, be sure to check out my course on FOSRESTBundle.

At this stage we haven't actually created a User entity to make our fos_user config work, but as you will soon see, this will follow shortly.

UserSetupContext - Not Behat Best Practice

It's important to point out that the following step is not Behat best practice - to the very best of my knowledge.

Behat contexts are all about the describing the situation in which the test is taking place. It might be that we have run our tests in the 'context' of our RESTful API, or a separate set of tests that run in the 'context' of a user browsing our site via the standard web browser.

With this in mind, it makes no sense to have a UserSetupContext. I accept this is wrong, but it helps me structure and organise my test files. Primarily I have one *SetupContext per entity / resource that will be in use. This works for me. You may wish to group all your setup into one file, or ignore this practice entirely.

As demonstrated in the video, we will use PHPSpec to describe and create our User entity. This is a standard FOSUserBundle User Entity, with the only difference being that we are controlling the UserInterface rather than relying on the one provided by FOSUserBundle.

By the end of this video we will have the following:

<?php

// src/AppBundle/Features/Context/UserSetupContext.php

namespace AppBundle\Features\Context;

use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\TableNode;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserManagerInterface;

class UserSetupContext implements Context, SnippetAcceptingContext
{
    /**
     * @var UserManagerInterface
     */
    private $userManager;
    /**
     * @var EntityManagerInterface
     */
    private $em;

    public function __construct(UserManagerInterface $userManager, EntityManagerInterface $em)
    {
        $this->userManager = $userManager;
        $this->em = $em;
    }

    /**
     * @Given there are Users with the following details:
     */
    public function thereAreUsersWithTheFollowingDetails(TableNode $users)
    {

    }
}

In the next part, we will populate the thereAreUsersWithTheFollowingDetails function.

Code For This Course

Get the code for this course.

Episodes