Test Driven APIs with Laravel
@michaelpeacock
What’s in store?
• Look at API concepts within Laravel
• Look at testing concepts within Laravel
• How we can gradually build an API with confidence in its functionality,
without ever opening an API client
@michaelpeacock
• Organiser of PHP North East
• Occasional author and speaker
• Freelance developer
• Consultant CTO for early stage SaaS start-ups
Laravel APIs
Lots for free
• Models & Collections > JSON Responses automatically
• Validation Failures > JSON Responses automatically
• Automatic handling of request and response headers (content-type,
etc)
Lots to of power to customise
• API Resources
• API Routing
• Resource Controllers
• API Resource Controllers
Laravel Tests
Database interaction
• RefreshDatabase: calls migrate:fresh (drops all tables and re-migrates)
• DatabaseTransactions: wraps test db calls in a transaction
HTTP Tests
Sample project
• API for user group
• Events
• Venues
• Talks
Model Integration Tests
Model integration tests
• A good starting point
• Give us confidence that models, migrations, relations and factories
are setup correctly
• Especially important if we are providing custom field names for
relations, or working with models outside of the root app directory
• Factories are important for API tests, so its good that we test the basis
setup before we move onto that.
• Inspired by Amo Chohan: 12 tried and tested top tips for better
testing
Key things to test initially
• We can instantiate the class
• Any relations
Migrations
Tip: Name your migrations well
Models
Factories
API Setup
Making requests
• $this->json(method, uri, data, headers)
• $this->getJson / postJson / patchJson / deleteJson
• $this->get(url, headers)
• $this->post(url, data, headers)
Testing responses: Status Codes
• $response->assertStatus(status code)
• $response->assertSuccessful()
• $response->assertOk()
• $response->assertNotFound()
• $response->assertNotFound()
• $response->assertForbidden()
GET /talks
API Controllers
php artisan make:controller Api/EventsController --model=Event --api
API Routing
Testing response data
• $response->assertExactJson(array)
• $response->assertJson(array)
• $response->assertJsonCount(array)
• $response->assertJsonFragment(array)
• $response->assertJsonMissing(array)
• $response->assertJsonMissingExact(array)
• $response->assertJsonStructure(array)
Testing headers
• $response->assertHeader(header, value)
• $response->assertHeaderMissing(header)
• $response->assertLocation(url) // Location header
Lets check we get a JSON response…
Better responses
• Venue name and description should be included
• Event id should not be included
• Start and end date should be formatted a specific way
Now we get some failing (not erroring) tests
Resources
php artisan make:resource Event
php artisan make:resource Venue
Passing tests again!
POST /events
• We now want to test creating an event
A test helper to build valid request data
Saving a new event
Status codes and Resources
Testing Validation
• $response->assertJsonMissingValidationErrors(array)
• $response->assertJsonValidationErrors()
Creating a new request
php artisan make:request CreateEventRequest
Use the request
Authenticating
actingAs
Thank you
• https://github.com/mkpeacock/talk-test-driven-apis-with-laravel

Test driven APIs with Laravel

Editor's Notes

  • #8 Support for unit, browser, HTTP, console tests.
  • #10 Discuss how these work, and the impact on database transactions (i.e. how that works)
  • #16 Explain about the relationships first, putting thought into the modelling of the app. Emphasise how many times I've been caught out by a migration where it was linked to the wrong FK because I copy and pasted a migration!
  • #23 We can then iterate between tests and code, adding in the relationships that we need, until we get passing tests.
  • #38 Returning a collection or a model, combined with API request headers, results in the data being converted to JSON
  • #39 By default the JSON response includes all fields in the model, we can test that we have some fields that we want
  • #45 Error: date fields are not cast as dates, fix that, then get failing tests.
  • #47 Resources: transformers for our eloquent models
  • #54 Failing test because nothing is created, its an error not a fail as such because we called findOrFail, in our test, so our test has thrown an exception