I'm having troubles with using the right workflow with TDD. Some people say we should design before writing any code, some say we should make a test, make it pass, then refactor the code and that should help our thinking to find the right design.
But everyone say you should have 10 times more unit tests than integration tests.
I'm comfortable starting by doing an integration test that'd do the whole user story, and then refactoring. But in the end I have almost only integration tests, and almost no unit tests.
Here's an example of the workflow I use :
Feature : A user should be able to register through the API at /register/, passing the email and a password.
First I'd do an integration test :
<?php
class RegisterUserTest extends TestCase
{
/** @test */
public function user_is_registered_when_email_is_valid()
{
$email = '[email protected]';
// Given the email was not registered before
$this->notSeeInDatabase('user', ['email' => $email]);
// When we post a request to /register/
$this->json('POST', '/register/', ['email' => $email]);
// Then we see the user is now registered
$this->seeInDatabase('user', ['email' => $email]);
}
}
Then I'd write the code to make this test pass (I'll only show the controller code) :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
class RegisterController extends Controller
{
public function registerUser(Request $request)
{
(new User)->create(['email' => $request->input('email')]);
}
}
Now that's enough to make the first test pass, so I'll stop there and make another test to prove that when the email is not valid it should not be inserted in database, and it should return a 422 response code :
<?php
class RegisterUserTest extends TestCase
{
//...
/** @test */
public function invalid_email_returns_a_422_and_user_is_not_registered()
{
// Given the email is invalid
$email = 'invalid';
// When we post a request to /register/
$this->json('POST', '/register/', ['email' => $email]);
// Then we see the response code is 422
$this->assertResponseStatus(422);
$this->notSeeInDatabase('user', ['email' => $email]);
}
}
Now I'd write the code to make it pass :
public function registerUser(Request $request)
{
$email = $request->input('email');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return response('Invalid Email', 422);
}
(new User)->create(['email' => $email]);
}
Now to me that's already too much logic in the controller so I'd refactor obviously :
public function registerUser(Request $request)
{
try {
$user = new User;
$user->setEmail($request->input('email'));
$user->save();
} catch (InvalidArgumentException $invalidArgumentException) {
return response($invalidArgumentException->getMessage(), 422);
}
}
You get the idea. I didn't have to do any unit testing there, my integration testing work fine and are enough there. I could go on and on without any needed unit testing.
So how/when do you start unit testing ?