Building a Phonegap app with Laravel and Angular - Part 1

Published by at 11th September 2016 6:33 pm

A lot of my work over the last few years has been on Phonegap apps. Phonegap isn't terribly hard to use, but the difference in context between that and a more conventional web app means that you have to move a lot of functionality to the client side, and unless you've used client-side Javascript frameworks before it can be a struggle.

In this series of tutorials I'll show you how I might build a Phonegap app. The work involved will include:

  • Building a REST API using Laravel to expose the data
  • Building an admin interface to manage the data
  • Building a Phonegap app using Angular.js
  • Testing and deploying it

In the process we'll cover issues like authentication, authorisation, real-time notifications and working with REST APIs. Note that we won't cover the app submission process - you can find plenty of resources on that. We will, however, be using Phonegap Build to build the app.

The brief

Let's say our new client is an animal shelter. The brief for the app is as follows:

My New Animal Friend will be an app for finding a new pet. Once a user signs in, they'll be able to choose what type of pet they're looking for, then look through a list of pets available to adopt. They can reject them by swiping left or save them by swiping right. They can see more about the ones they swipe right on, and arrange to meet them, from within the app. Users can also message the staff to ask questions about a pet.

Nice idea, but there's a lot of work involved! Our very first task is to build the REST API, since everything else relies on that. Before starting, make sure you have the following installed:

  • PHP (I'm using PHP 7, but 5.6 should be fine)
  • Composer
  • Git
  • A compatible relational database (I use PostgreSQL)
  • Redis
  • Your usual text editor
  • Node.js

As long as you have this, you should be ready to go. Using Homestead is the simplest way to get started if you don't have all this stuff already.

Starting the API

To start building our REST API, run the following command from the shell:

$ composer create-project --prefer-dist laravel/laravel mynewanimalfriend-backend

We also have some other dependencies we need to install, so switch into the new directory and run the following command:

$ composer require barryvdh/laravel-cors tymon/jwt-auth predis/predis

Next, we need to add the new packages to the Laravel config. Open up config/app.php and add the following to the providers array:

1 Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
2 Barryvdh\Cors\ServiceProvider::class,

And the following to the aliases array:

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,

We also need to ensure that the CORS middleware is applied to all API routes. Open up app/Http/Kernel.php and under the api array in protected $middlewareGroups paste the following:

\Barryvdh\Cors\HandleCors::class,

Now that the packages are included, we can publish the files for them:

$ php artisan vendor:publish

Next, we need to set a key for our API authentication:

$ php artisan jwt:generate

And set a custom namespace:

$ php artisan app:name AnimalFriend

You'll also want to set up the .env file with the configuration settings for your application. There's one at .env.example by default that you can copy and customise. Then run the following command to generate the application key:

$ php artisan key:generate

I had to change the namespace for the user model in config/jwt.php as well:

'user' => 'AnimalFriend\User',

I also tend to amend the settings in phpunit.xml as follows so that it uses an in-memory SQLite database for tests:

1 <env name="APP_ENV" value="testing"/>
2 <env name="SESSION_DRIVER" value="array"/>
3 <env name="QUEUE_DRIVER" value="sync"/>
4 <env name="CACHE_DRIVER" value="redis"/>
5 <env name="DB_CONNECTION" value="sqlite"/>
6 <env name="DB_DATABASE" value=":memory:"/>

Also, delete tests/ExampleTest.php and amend tests/TestCase.php as follows in order to use database migrations in tests:

1<?php
2
3use Illuminate\Foundation\Testing\DatabaseMigrations;
4
5abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
6{
7 use DatabaseMigrations;
8
9 /**
10 * The base URL to use while testing the application.
11 *
12 * @var string
13 */
14 protected $baseUrl = 'http://localhost';
15
16 /**
17 * Creates the application.
18 *
19 * @return \Illuminate\Foundation\Application
20 */
21 public function createApplication()
22 {
23 $app = require __DIR__.'/../bootstrap/app.php';
24
25 $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
26
27 return $app;
28 }
29}

With that in place, we can start work on our API proper.

Authenticating our API

We're going to start out with a very limited subset of our API. First, we'll implement the authentication for our app, then we'll add the facility to view a list of pets or an individual pet. Other functionality will come later. This will be sufficient to get the app working.

First, we need to create our user model. As we'll be practising TDD throughout, we write a test for the user model first. Save the following as tests/UserModelTest.php:

1<?php
2
3use AnimalFriend\User;
4
5class UserModelTest extends TestCase
6{
7 /**
8 * Test creating a user
9 *
10 * @return void
11 */
12 public function testCreatingAUser()
13 {
14 // Create a User
15 $user = factory(AnimalFriend\User::class)->create([
16 'name' => 'bobsmith',
17 'email' => 'bob@example.com',
18 ]);
19 $this->seeInDatabase('users', ['email' => 'bob@example.com']);
20
21 // Verify it works
22 $saved = User::where('email', 'bob@example.com')->first();
23 $this->assertEquals($saved->id, 1);
24 $this->assertEquals($saved->name, 'bobsmith');
25 }
26}

If we run the tests:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4. 1 / 1 (100%)
5
6Time: 169 ms, Memory: 12.00MB
7
8OK (1 test, 3 assertions)

We already have a perfectly good User model and the appropriate migrations, so our test already passes.

Next, we need to implement the authentication system. Save this as tests/AuthTest.php:

1<?php
2
3use Illuminate\Foundation\Testing\DatabaseMigrations;
4
5class AuthTest extends TestCase
6{
7 use DatabaseMigrations;
8
9 /**
10 * Test the auth
11 *
12 * @return void
13 */
14 public function testAuth()
15 {
16 // Create a User
17 $user = factory(AnimalFriend\User::class)->create([
18 'name' => 'bobsmith',
19 'email' => 'bob@example.com',
20 'password' => bcrypt('password')
21 ]);
22
23 // Create request
24 $data = array(
25 'email' => $user->email,
26 'password' => 'password',
27 );
28 $response = $this->call('POST', 'api/authenticate', $data);
29 $this->assertResponseStatus(200);
30 $content = json_decode($response->getContent());
31 $this->assertTrue(array_key_exists('token', $content));
32 }
33
34 /**
35 * Test the auth when user does not exist
36 *
37 * @return void
38 */
39 public function testAuthFailure()
40 {
41 // Create data for request
42 $data = array(
43 'email' => 'user@example.com',
44 'password' => 'password',
45 );
46 $response = $this->call('POST', 'api/authenticate', $data);
47
48 // Check the status code
49 $this->assertResponseStatus(401);
50 }
51}

The first test creates a user and sends an authentication request, then confirms that it returns the JSON Web Token. The second checks that a user that doesn't exist cannot log in.

Let's run the tests:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4FF. 3 / 3 (100%)
5
6Time: 328 ms, Memory: 14.00MB
7
8There were 2 failures:
9
101) AuthTest::testAuth
11Expected status code 200, got 404.
12Failed asserting that 404 matches expected 200.
13
14/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
15/home/matthew/Projects/mynewanimalfriend-backend/tests/AuthTest.php:29
16
172) AuthTest::testAuthFailure
18Expected status code 401, got 404.
19Failed asserting that 404 matches expected 401.
20
21/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
22/home/matthew/Projects/mynewanimalfriend-backend/tests/AuthTest.php:49
23
24FAILURES!
25Tests: 3, Assertions: 5, Failures: 2.

With a failing test in place, we can implement login. First let's create our controller at app/Http/Controllers/AuthenticateController.php:

1<?php
2
3namespace AnimalFriend\Http\Controllers;
4
5use Illuminate\Http\Request;
6
7use AnimalFriend\Http\Requests;
8use AnimalFriend\Http\Controllers\Controller;
9use JWTAuth;
10use Tymon\JWTAuth\Exceptions\JWTException;
11use AnimalFriend\User;
12use Hash;
13
14class AuthenticateController extends Controller
15{
16 private $user;
17
18 public function __construct(User $user) {
19 $this->user = $user;
20 }
21
22 public function authenticate(Request $request)
23 {
24 // Get credentials
25 $credentials = $request->only('email', 'password');
26
27 // Get user
28 $user = $this->user->where('email', $credentials['email'])->first();
29
30 try {
31 // attempt to verify the credentials and create a token for the user
32 if (! $token = JWTAuth::attempt($credentials)) {
33 return response()->json(['error' => 'invalid_credentials'], 401);
34 }
35 } catch (JWTException $e) {
36 // something went wrong whilst attempting to encode the token
37 return response()->json(['error' => 'could_not_create_token'], 500);
38 }
39
40 // all good so return the token
41 return response()->json(compact('token'));
42 }
43}

And we need to set up the route in routes/api.php:

1<?php
2
3use Illuminate\Http\Request;
4
5/*
6|--------------------------------------------------------------------------
7| API Routes
8|--------------------------------------------------------------------------
9|
10| Here is where you can register API routes for your application. These
11| routes are loaded by the RouteServiceProvider within a group which
12| is assigned the "api" middleware group. Enjoy building your API!
13|
14*/
15
16Route::post('authenticate', 'AuthenticateController@authenticate');

Note that because it's an API route, it's automatically prefixed with api/ without us having to do anything.

Now if we run our tests, they should pass:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4... 3 / 3 (100%)
5
6Time: 402 ms, Memory: 14.00MB
7
8OK (3 tests, 6 assertions)

Now we can obtain a JSON Web Token to authenticate users with. To start with we'll only support existing users, but later we'll add a method to sign up. However, we need at least one user to test with, so we'll create a seeder for that at database/seeds/UserTableSeeder.php:

1<?php
2
3use Illuminate\Database\Seeder;
4use Carbon\Carbon;
5
6class UserTableSeeder extends Seeder
7{
8 /**
9 * Run the database seeds.
10 *
11 * @return void
12 */
13 public function run()
14 {
15 // Add user
16 DB::table('users')->insert([
17 'name' => 'bobsmith',
18 'email' => 'bob@example.com',
19 'created_at' => Carbon::now(),
20 'updated_at' => Carbon::now(),
21 'password' => Hash::make("password")
22 ]);
23 }
24}

You can run php artisan make:seeder UserTableSeeder to generate the file, or just paste it in. You also need to amend database/seeds/DatabaseSeeder.php as follows:

1<?php
2
3use Illuminate\Database\Seeder;
4
5class DatabaseSeeder extends Seeder
6{
7 /**
8 * Run the database seeds.
9 *
10 * @return void
11 */
12 public function run()
13 {
14 $this->call(UserTableSeeder::class);
15 }
16}

This ensures the seeder will actually be called. Then, run the following commands:

1$ php artisan migrate
2$ php artisan db:seed

That sets up our user in the database.

Adding the Pets endpoint

Our next step is to add the pets model and endpoint. Our Pet model should have the following fields:

  • ID
  • Timestamps (created_at and updated_at)
  • Name
  • Path to photo
  • Availability
  • Type (eg cat, dog)

Let's create a test for that model:

1<?php
2
3use AnimalFriend\Pet;
4
5class PetModelTest extends TestCase
6{
7 /**
8 * Test creating a pet
9 *
10 * @return void
11 */
12 public function testCreatingAPet()
13 {
14 // Create a Pet
15 $pet = factory(AnimalFriend\Pet::class)->create([
16 'name' => 'Freddie',
17 'type' => 'Cat',
18 ]);
19 $this->seeInDatabase('pets', ['type' => 'Cat']);
20
21 // Verify it works
22 $saved = Pet::where('name', 'Freddie')->first();
23 $this->assertEquals($saved->id, 1);
24 $this->assertEquals($saved->name, 'Freddie');
25 $this->assertEquals($saved->type, 'Cat');
26 $this->assertEquals($saved->available, 1);
27 $this->assertEquals($saved->picture, '1.jpg');
28 }
29}

Save this as tests/PetModelTest.php. Then run the tests:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4..E. 4 / 4 (100%)
5
6Time: 414 ms, Memory: 16.00MB
7
8There was 1 error:
9
101) PetModelTest::testCreatingAUser
11InvalidArgumentException: Unable to locate factory with name [default] [AnimalFriend\Pet].
12
13/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:126
14/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2280
15/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:139
16/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:106
17/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:84
18/home/matthew/Projects/mynewanimalfriend-backend/tests/PetModelTest.php:16
19
20ERRORS!
21Tests: 4, Assertions: 6, Errors: 1.

First we need to create a factory for creating a pet in database/factories/ModelFactory.php:

1$factory->define(AnimalFriend\Pet::class, function (Faker\Generator $faker) {
2 return [
3 'name' => $faker->firstNameMale,
4 'type' => 'Cat',
5 'available' => 1,
6 'picture' => '1.jpg'
7 ];
8});

Then, we create the model:

$ php artisan make:model Pet

Next, we create a migration for the Pet model:

1$ php artisan make:migration create_pets_table
2Created Migration: 2016_09_11_145010_create_pets_table

And paste in the following code:

1<?php
2
3use Illuminate\Support\Facades\Schema;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Database\Migrations\Migration;
6
7class CreatePetsTable extends Migration
8{
9 /**
10 * Run the migrations.
11 *
12 * @return void
13 */
14 public function up()
15 {
16 Schema::create('pets', function (Blueprint $table) {
17 $table->increments('id');
18 $table->string('name');
19 $table->string('type');
20 $table->string('available');
21 $table->string('picture')->nullable();
22 $table->timestamps();
23 });
24 }
25
26 /**
27 * Reverse the migrations.
28 *
29 * @return void
30 */
31 public function down()
32 {
33 Schema::drop('pets');
34 }
35}

Time to run the tests again:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4.... 4 / 4 (100%)
5
6Time: 412 ms, Memory: 16.00MB
7
8OK (4 tests, 12 assertions)

With that done, we can start work on implementing the endpoint. We need to check that unauthorised users cannot retrieve the data, and that authorised users can. First, let's create tests/PetControllerTest.php:

1<?php
2
3use Illuminate\Foundation\Testing\DatabaseMigrations;
4
5class PetControllerTest extends TestCase
6{
7 use DatabaseMigrations;
8
9 /**
10 * Test fetching pets when unauthorised
11 *
12 * @return void
13 */
14 public function testFetchingPetsWhenUnauthorised()
15 {
16 // Create a Pet
17 $pet = factory(AnimalFriend\Pet::class)->create([
18 'name' => 'Freddie',
19 'type' => 'Cat',
20 ]);
21 $this->seeInDatabase('pets', ['type' => 'Cat']);
22
23 // Create request
24 $response = $this->call('GET', '/api/pets');
25 $this->assertResponseStatus(400);
26 }
27
28 /**
29 * Test fetching pets when authorised
30 *
31 * @return void
32 */
33 public function testFetchingPets()
34 {
35 // Create a Pet
36 $pet = factory(AnimalFriend\Pet::class)->create([
37 'name' => 'Freddie',
38 'type' => 'Cat',
39 ]);
40 $this->seeInDatabase('pets', ['type' => 'Cat']);
41
42 // Create a User
43 $user = factory(AnimalFriend\User::class)->create([
44 'name' => 'bobsmith',
45 'email' => 'bob@example.com',
46 ]);
47 $this->seeInDatabase('users', ['email' => 'bob@example.com']);
48
49 // Create request
50 $token = JWTAuth::fromUser($user);
51 $headers = array(
52 'Authorization' => 'Bearer '.$token
53 );
54
55 // Send it
56 $this->json('GET', '/api/pets', [], $headers)
57 ->seeJsonStructure([
58 '*' => [
59 'id',
60 'name',
61 'type',
62 'available',
63 'picture',
64 'created_at',
65 'updated_at'
66 ]
67 ]);
68 $this->assertResponseStatus(200);
69 }
70}

First, we create a pet, make an HTTP request to /api/pets, and check we are not authorised. Next, we do the same, but also create a user and a JSON Web Token, and pass the token through in the request. Then we verify the response data and that it was successful.

Let's run the tests:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4..FF.. 6 / 6 (100%)
5
6Time: 509 ms, Memory: 16.00MB
7
8There were 2 failures:
9
101) PetControllerTest::testFetchingPetsWhenUnauthorised
11Expected status code 400, got 404.
12Failed asserting that 404 matches expected 400.
13
14/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
15/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:25
16
172) PetControllerTest::testFetchingPets
18Failed asserting that null is of type "array".
19
20/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:295
21/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:67
22
23FAILURES!
24Tests: 6, Assertions: 17, Failures: 2.

That looks correct, so we can start building our endpoint. We can generate a boilerplate for it as follows:

$ $ php artisan make:controller PetController --resource

Note the --resource flag - this tells Laravel to set it up to be a RESTful controller with certain predefined functions. Next, let's amend the new file at app\Http\Controllers/PetController.php as follows:

1<?php
2
3namespace AnimalFriend\Http\Controllers;
4
5use Illuminate\Http\Request;
6
7use AnimalFriend\Http\Requests;
8use AnimalFriend\Pet;
9
10class PetController extends Controller
11{
12 private $pet;
13
14 public function __construct(Pet $pet) {
15 $this->pet = $pet;
16 }
17
18 /**
19 * Display a listing of the resource.
20 *
21 * @return \Illuminate\Http\Response
22 */
23 public function index()
24 {
25 // Get all pets
26 $pets = $this->pet->get();
27
28 // Send response
29 return response()->json($pets, 200);
30 }
31}

This implements an index route that shows all pets. Next, we hook up the route in routes/api.php:

1// Auth routes
2Route::group(['middleware' => ['jwt.auth']], function () {
3 Route::resource('pets', 'PetController');
4});

Note that we wrap this resource in the jwt.auth middleware to prevent access by unauthorised users. Implementing this as middleware makes it very easy to reuse. Also note that we can specify it as a resource, meaning we don't have to explicitly hook up each route to a controller method.

Let's run the tests again:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4..EE.. 6 / 6 (100%)
5
6Time: 511 ms, Memory: 16.00MB
7
8There were 2 errors:
9
101) PetControllerTest::testFetchingPetsWhenUnauthorised
11ReflectionException: Class jwt.auth does not exist
12
13/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Container/Container.php:734
14/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Container/Container.php:629
15/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:709
16/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:173
17/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:517
18/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:24
19
202) PetControllerTest::testFetchingPets
21ReflectionException: Class jwt.auth does not exist
22
23/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Container/Container.php:734
24/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Container/Container.php:629
25/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:709
26/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:173
27/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:517
28/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:72
29/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:56
30
31ERRORS!
32Tests: 6, Assertions: 15, Errors: 2.

Looks like JWT isn't configured correctly. We can fix that in app/Http/Kernel.php by adding it to $routeMiddleware:

1 'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
2 'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',

And run the tests again:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4...... 6 / 6 (100%)
5
6Time: 514 ms, Memory: 16.00MB
7
8OK (6 tests, 25 assertions)

Our final task for today on the API is building a route for fetching a single pet. Our tests need to handle three situations:

  • An unauthorised request
  • A request for a pet that does not exist
  • A request for a pet that does exist

Add these methods to tests/PetControllerTest.php:

1 /**
2 * Test fetching pet when unauthorised
3 *
4 * @return void
5 */
6 public function testFetchingPetWhenUnauthorised()
7 {
8 // Create a Pet
9 $pet = factory(AnimalFriend\Pet::class)->create([
10 'name' => 'Freddie',
11 'type' => 'Cat',
12 ]);
13 $this->seeInDatabase('pets', ['type' => 'Cat']);
14
15 // Send request
16 $response = $this->call('GET', '/api/pets/'.$pet->id);
17 $this->assertResponseStatus(400);
18 }
19
20 /**
21 * Test fetching pet which does not exist
22 *
23 * @return void
24 */
25 public function testFetchingPetDoesNotExist()
26 {
27 // Create a User
28 $user = factory(AnimalFriend\User::class)->create([
29 'name' => 'bobsmith',
30 'email' => 'bob@example.com',
31 ]);
32 $this->seeInDatabase('users', ['email' => 'bob@example.com']);
33
34 // Create request
35 $token = JWTAuth::fromUser($user);
36 $headers = array(
37 'Authorization' => 'Bearer '.$token
38 );
39
40 // Send it
41 $this->json('GET', '/api/pets/1', [], $headers);
42 $this->assertResponseStatus(404);
43 }
44
45 /**
46 * Test fetching pet when authorised
47 *
48 * @return void
49 */
50 public function testFetchingPet()
51 {
52 // Create a Pet
53 $pet = factory(AnimalFriend\Pet::class)->create([
54 'name' => 'Freddie',
55 'type' => 'Cat',
56 ]);
57 $this->seeInDatabase('pets', ['type' => 'Cat']);
58
59 // Create a User
60 $user = factory(AnimalFriend\User::class)->create([
61 'name' => 'bobsmith',
62 'email' => 'bob@example.com',
63 ]);
64 $this->seeInDatabase('users', ['email' => 'bob@example.com']);
65
66 // Create request
67 $token = JWTAuth::fromUser($user);
68 $headers = array(
69 'Authorization' => 'Bearer '.$token
70 );
71
72 // Send it
73 $this->json('GET', '/api/pets/'.$pet->id, [], $headers)
74 ->seeJsonStructure([
75 'id',
76 'name',
77 'type',
78 'available',
79 'picture',
80 'created_at',
81 'updated_at'
82 ]);
83 $this->assertResponseStatus(200);
84 }

Let's check our tests fail:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4.....FE.. 9 / 9 (100%)
5
6Time: 974 ms, Memory: 16.00MB
7
8There was 1 error:
9
101) PetControllerTest::testFetchingPet
11PHPUnit_Framework_Exception: Argument #2 (No Value) of PHPUnit_Framework_Assert::assertArrayHasKey() must be a array or ArrayAccess
12
13/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:304
14/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:145
15
16--
17
18There was 1 failure:
19
201) PetControllerTest::testFetchingPetDoesNotExist
21Expected status code 404, got 400.
22Failed asserting that 400 matches expected 404.
23
24/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
25/home/matthew/Projects/mynewanimalfriend-backend/tests/PetControllerTest.php:112
26
27ERRORS!
28Tests: 9, Assertions: 31, Errors: 1, Failures: 1.

Now, we already have the show() method hooked up by default, so we just have to implement it in app/Http/Controllers/PetController.php:

1 /**
2 * Display the specified resource.
3 *
4 * @param int $id
5 * @return \Illuminate\Http\Response
6 */
7 public function show($id)
8 {
9 // Get pet
10 $pet = $this->pet->findOrFail($id);
11
12 // Send response
13 return response()->json($pet, 200);
14 }

And let's run our tests again:

1$ vendor/bin/phpunit
2PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
3
4......... 9 / 9 (100%)
5
6Time: 693 ms, Memory: 16.00MB
7
8OK (9 tests, 39 assertions)

Now we have all the endpoints we need to get started with the app. You can find the source code for this backend on Github - check out the lesson-1 tag.

That seems like a good place to stop for now. We have our first pass at the back end. It's not complete by any means, but it's a good start, and is sufficient for us to get some basic functionality up and running in the app. In the next instalment we'll start working with Phonegap to build the first pass at the app itself. Later instalments will see us working with both the app and backend to build it into a more useful whole.