I've been coding as a career for about 2 years now but am just now writing my first "real" tests for a non-trivial application. I've been trying to follow best practices I've picked up from the internet here and there but I would like some feedback on whether I am heading in the right direction with what I am doing and what I can do to correct/improve my process.
The application is a headless API that will be seeing public (aka clients who want to integrate with our API, not totally public) consumption in a few months.
From top to bottom the stack and layers look like this:
- RESTful API (JSON)
- Oauth 2.0 authentication
- (Mostly) CRUD controllers (PHP, Laravel 5)
- Doctrine ORM for DAL
- MySQL DB
I wanted to design my tests to be as simple but thorough as possible. However I don't have eternity and a day to write really fundamental test (unit tests I guess) for all controllers and all business logic so I decided to take advantage of laravel's built in mocking for routes and test all endpoints for our API.
My line of thinking is that, since we have standard data structures and formats for request/response (JSON, defined json schemas for objects, etc.) I could emulate requests knowing what the response should look like and compare the two.
EX. GET api/user/1
should return user 1
as a json object that I can compare for equality with the serialized json the actual object gives me.
So the actual testing looks like this (using PHPunit):
- On application start Laravel sets the app environment to "testing" which changes the default DB connection to a SQLite file
- On PHPunit boot Doctrine gets model metadata and creates tables for my domain in the DB, some extra scripts for extra-domain stuff (oauth tables) create those as well
- Alice, an object generator for doctrine, creates a decently diverse set of domain entities with random data from Faker and hydrates the SQLite DB. All created entities are returned and I store these in a global variable for later use.
- I make a copy of the hydrated, untouched DB to use in tests that involve write operations so I can reset the db after each test.
- Oauth clients get generated for users with different roles so I can test oauth scopes and permissions across different users for each test
- I use Laravel's route mocking to call each API endpoint and
- For read-only tests (GETs) I use the json serialized form of objects stored earlier (step 3) to test the equality of responses,
- For write tests I construct requests from our object json schemas, then build a real object that should equate it, make the request and then check the response for equality with the serialized form of the object (like step 7). After each write test the DB is overwritten with the original hydrated DB from step 4 for the next test.
So that's the gist of it. Using this approach has given me fantastic performance -- running 26 tests with 87 assertions takes ~12 seconds.
My questions/concerns
- I don't mock anything. I feel like I'm doing something wrong because of this but at the same time I am actually providing a "real" environment for the whole application so nothing seems like it needs to be mocked. Is this okay?
- Am I trying to do too much with each test? I've read articles that warn against having too many dependencies in a test because it makes the test fragile and less effective, but all of the "dependencies" in my tests are actual code -- so if a test breaks it means my actual code is broken which I feel I would want to know.
What else can you tell me? Suggestions? How can I improve this? Thanks!