Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Test Not Validating Database Constraint

I have a Laravel 5.3 app that includes a users table. One of the fields on that table is api_token, which in the database definition is set to NOT NULL. If I use Postman and hit my API endpoint for creating a user and don't include the api_token field, I receive the following error: SQLSTATE[HY000]: General error: 1364 Field 'api_token' doesn't have a default value.

I haven't added code to the generate the token yet, so that's exactly what I'd expect. However, I have this PHPUnit test:

/** @test */
public function a_user_can_be_created()
{
    $this->user_data = [
        'first_name' => 'Jane',
        'last_name' => 'Smith',
        'email_address' => '[email protected]',
        'phone' => '1234567890',
        'username' => 'jsmith',
        'password' => 'testing'
    ];

    $this->post('/api/users', $this->user_data, array(
                'Accept' => 'application/json'
            ))
         ->assertResponseStatus(201)
         ->seeJsonEquals(['status' => 'success'])
         ->seeInDatabase('users', $this->user_data);
}

The problem is that the test passes. How can this test be successful when the database should be throwing an exception when the user is saved?

I've 'died and dumped' at various points and confirmed that the data is in fact being saved to the database when the automated test runs. I've also tried using the same DB for both Postman and the automated test (instead of a separate testing DB) - same result.

like image 887
jackel414 Avatar asked Nov 10 '16 16:11

jackel414


2 Answers

I tried to build the minimal version of your API and create a similar test on the latest Laravel 5.3.22 version. I also used MariaDB 10.1.13 for the database. And it turns out the test failed as we expected, because of the empty api_token.

1. Users migration file

Here's my migration file for creating the users table:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('first_name');
            $table->string('last_name');
            $table->string('email_address')->unique();
            $table->string('phone');
            $table->string('username');
            $table->string('password');
            $table->string('api_token');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

2. The User model

Here's my app\User.php file looks like. Pretty minimal, I only update the $fillable property to match our users table structure:

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'first_name',
        'last_name',
        'email_address',
        'phone',
        'username',
        'password',
        'api_token',
    ];

    protected $hidden = [
        'password',
    ];
}

3. The API routes file

I create a new API route on routes/api.php file to create a new user. Super simple without any validation involved:

Route::post('users', function (Request $request) {
    $data = $request->only([
        'first_name',
        'last_name',
        'email_address',
        'phone',
        'username',
        'password',
        'api_token',
    ]);

    if (is_string($data['password'])) {
        $data['password'] = bcrypt($data['password']);
    }

    $user = App\User::create($data);

    return response()->json([
        'status' => 'success',
    ], 201);
});

4. Create user API test

And finally here's my test file for testing our create user API.

class ApiTest extends TestCase
{
    /** @test */
    public function a_user_can_be_created()
    {
        $user = [
            'first_name' => 'Jane',
            'last_name' => 'Smith',
            'email_address' => '[email protected]',
            'phone' => '1234567890',
            'username' => 'jsmith',
        ];

        $this->json('POST', 'api/users', $user)
            ->assertResponseStatus(201)
            ->seeJsonEquals(['status' => 'success'])
            ->seeInDatabase('users', array_except($user, ['password']));
    }
}

The only difference is I used array_except() to exclude the password column from database testing since we hashed the value before storing it in the database. But of course this does not relate to your issue.

When I run the test without the api_token being set, the test failed as expected. The API return a status code of 500, while we expected it to be 201.

The create user API failed!

Also as a reference, when I set the api_token column as NOT nullable, then force to create a new user with an empty api_token, the exception message I received is like this:

QueryException in Connection.php line 769:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'api_token' cannot be null (SQL: insert into users...

Hope this help you debug your issue!

like image 83
Risan Bagja Pradana Avatar answered Oct 27 '22 08:10

Risan Bagja Pradana


I assume you are using mysql. In mysql there is a problem with NOT NULL. It goes like this. Although the field is NOT NULL you can leave it empty in mysql and the query passes. NOT NULL in mysql means that you can't have NULL value in that field not that you can't leave it empty. I assume you create the user as this

User::create($request->all()) 

If you try to print the generated sql query

dd((new User($request->all())->toSql())

You will see that the generated query contains '' value for api_token field

I switched to posgres mainly because this problem.

If you want to solve this problem you have couple of options. One is to leave the api_token field nullable. The second one is to fill the model manually

like image 2
Kliment Avatar answered Oct 27 '22 08:10

Kliment