From Fake To Real Data on Back End and Front


In this video we are going to take a whistle-stop tour of several core pieces of the TweetHours Ionic mobile app.

These include:

  • the GET end-point for /api/tweet-hours/ (it should return all TweetHours)
  • write a Behat 3 feature, feature context, and refreshing our Database with some fresh Faker data every test run
  • Using Angular $http to get our data inside the app itself

Let's get started.

Fake It Till You Make It

If you've been watching along, you'll know that we had created our fake data on the front end already. It looks a little like this:

[
    {"id": 1 , "title":"1hour" , "day":"Monday" , "start":"14:22" , "duration": 1 },
    {"id": 2 , "title":"2hour" , "day":"Monday" , "start":"14:22" , "duration": 12 },
    {"id": 3 , "title":"3hour" , "day":"Monday" , "start":"14:23" , "duration": 1 },
    {"id": 4 , "title":"4hour" , "day":"Monday" , "start":"14:24" , "duration": 1 },
    {"id": 5 , "title":"5hour" , "day":"Monday" , "start":"14:25" , "duration": 1 },
    {"id": 6 , "title":"ahour" , "day":"Monday" , "start":"14:25" , "duration": 1 },
    {"id": 7 , "title":"bhour" , "day":"Monday" , "start":"14:24" , "duration": 1 },
    {"id": 8 , "title":"chour" , "day":"Monday" , "start":"14:24" , "duration": 1 },
    {"id": 9 , "title":"dhour" , "day":"Monday" , "start":"14:26" , "duration": 1 },
    {"id": 10 , "title":"ehour" , "day":"Monday" , "start":"14:27" , "duration": 1 }
]

And we have been pulling that in from a separate file / its own function, depending on the stage of our project so far.

Now we need to make the API actually provide that data, for real.

If you care about your code quality in any way, the first thing to do in this situation is to create a test suite that covers this situation.

I am making a business decision that in order to get this product to market and start collecting some feedback on whether it's even worth pursuing, the amount of these tests are the bare minimum. I understand the code I am using and trust it to behave when used the way I am using it (that is to say, I am re-using this code and I know it worked last time).

However, do not follow this blindly if you do not understand exactly what is happening. Use a proper testing set-up like Codeception + PHPUnit or Behat + PHPSpec and ensure you know how each component part behaves in isolation. Then, slotting them together and getting the desired outcome is much, much... MUCH easier.

Let's see the Behat feature specification at this stage:

# src/ApiBundle/Features/hours.get.feature
Feature: Tweet Hours API - GET

  In order to retrieve the list of TweetHours
  As a developer
  I need to GET Tweet Hours

  Background:
    Given there are 10 Tweet Hours

    Scenario: GET all Tweet Hours
      When I send a GET request to "/api/tweet-hours/"
      Then the response code should be 200
        And I should see json representation of 10 tweet hours

Whilst it's not explicit, this feature implies to a developer that there should be some sort of database - whether that's an in-memory data store for the purposes of testing, or a real database, the feature doesn't actually say. It's up to us as developers to just provide what this tells us to do.

If you're uncomfortable with using Symfony 2 with FoSRESTBundle as a RESTful API then please check out my full tutorial series on it. It will get you up and running in no time.

Putting Things In Context

There are many ways to fake data when testing. One such way is to use a bundle like Will Durand's Faker Bundle to pre-populate our database, then reload that known state each time we need to run a new test. That's outside the scope of this video, but I have covered it before on Code Review.

Another is to create the data on demand. That's my current preferred way of doing things in real world projects, and this is as real world as it gets!

All this happens inside our feature context file. Using Behat 3's Symfony2 Extension (thank you to Everzet, Stof, Ciaran McNulty and all other contributors for this fine extension) we can easily inject the entity manager into our context, allowing us to efficiently save off via Doctrine, the way any Symfony2 bod would know and love.

    /**
     * @Given there are :count Tweet Hours
     */
    public function thereAreTweetHours($count)
    {
        $faker = Faker\Factory::create();

        for ($x=0; $x<$count; ++$x) {
            $hour = new Hour();
            $hour->setTitle($faker->lexify('????????'));
            $hour->setDay($faker->dayOfWeek());
            $hour->setStartsAt($faker->time('H:i'));
            $hour->setDuration(1);

            $this->em->persist($hour);
        }

        $this->em->flush();
    }

Implementing the rest of the scenario (no pun intended) is an straightforward copy / paste / rename exercise from the way we covered in the earlier tutorial series.

We should just need to make our entity match up with the exact same data we have been faking in our app API call ;)

Changing From Mock To Real Front End Data

Once our Behat 3 feature green and passing, the next concern is changing the way our Angular / Ionic hybrid mobile app is pulling in its data set.

We want to swap out our existing on-device data set with a dynamic dataset that's being pulled in from the Internet. Or is that the cloud? That sounds exactly like the cloud. Let's put some marketing spin on this and say, you know what? We want to pull in our data from the cloud.

Not a whole lot should have to change on our app, as it's been designed to expect this change at some point in the development life cycle.

The main problem we could anticipate is switching out accessing a static file vs the expecting a JavaScipt promise to be handled in our code. That's the hairy part. Until you've seen it done, and it's not so bad after all.

# app/modules/core/services/api-lookup.service.js
'use strict';

angular.module('core')

    .service('ApiLookup', ['$http', function($http) {

        this.getAll = function() {
            return $http.get('http://192.168.1.189/app_dev.php/api/tweet-hours/')
                .then(function(hours) {
                    return hours;
                });
        };
    }])
;

Angular is designed for exactly this kind of thing. $http comes free, we just inject it and use it.

I also needed to add my server IP address to the Content Security Policy in my index.html file:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' http://192.168.1.189 ... *snip*

After that, it was plain sailing.

Get your RESTful API to spit out exactly what your front end is expecting and the best part is, you can get one dev on the side you don't enjoy whilst you take care of the other.

Or, learn both and see which you prefer?

Or... just love doing both :) Each is completely different in how they feel, but being interested in both will make your creativity explode.

Appendix

Composer deps at this stage:

    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*",
        "doctrine/orm": "~2.2,>=2.2.3,<2.5",
        "doctrine/dbal": "<2.5",
        "doctrine/doctrine-bundle": "~1.2",
        "twig/extensions": "~1.0",
        "symfony/assetic-bundle": "~2.3",
        "symfony/swiftmailer-bundle": "~2.3",
        "symfony/monolog-bundle": "~2.4",
        "sensio/distribution-bundle": "~3.0,>=3.0.12",
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2",
        "incenteev/composer-parameter-handler": "~2.0",

        "friendsofsymfony/rest-bundle": "1.5.*@dev",
        "jms/serializer-bundle": "0.13.*@dev",
        "knplabs/doctrine-behaviors": "1.1.*@dev",
        "nelmio/api-doc-bundle": "2.7.*@dev",
        "nelmio/cors-bundle": "1.4.*@dev",
        "symfony/intl": "3.0.*@dev"
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3",

        "behat/behat": "3.0.*@dev",
        "behat/symfony2-extension": "~2.0@dev",
        "behat/web-api-extension" : "dev-master",
        "mockery/mockery": "1.0.*@dev",
        "phpspec/phpspec": "2.2.*@dev",
        "phpunit/phpunit": ">=4.0.0,<4.6.0",
        "fzaninotto/faker": "1.5.0"
    },

Episodes