Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Lars-Janssen's avatar

Super weird test failure with assertDatabaseHas

Hello,

I've got a super weird thing going on for 3 days now. And I can't get it to work.

I've got 2 tables:

Company
Products

And a pivot table company_products.

So in my webapp everything is working. However in my test it keeps failing.

This is the test:

/** @test */
public function when_a_product_is_destroyed_linked_companies_will_go_away()
{
    $product = create(Product::class);
    create(Company::class)->syncProducts([$product->toArray()]);

    $this->actingAs($this->user)->deleteJson('/producten/'.$product->id);

    $this->assertDatabaseMissing('company_products', [
        'product_id' => '1',
        'company_id' => '1',
    ]);

Pretty simple I create a product and sync it with a company. Then I destroy the product and check if the relations are gone in the pivot table. This is the exact failure I get:

There was 1 failure:

1) Tests\Feature\Product\DeleteProductTest::when_a_product_is_destroyed_linked_companies_will_go_away
Failed asserting that a row in the table [company_products] does not match the attributes {
    "product_id": "1",
    "company_id": "1"
}.

Found: [
    {
        "product_id": "1",
        "company_id": "1"
    }
].

So I've checked it a 1000 times manually in my webapp and the relation goes away in my pivot table (I use foreign keys). But the test keeps failing.

The controller method destroy looks like this:

/**
 * Destroy a product.
 *
 * @param Product $product
 * @return \Illuminate\Http\JsonResponse
 */
public function destroy(Product $product)
{
    $this->productRepo->delete($product->id);

    return response()->json(['status' => 'Verwijderd']);
}

When I dd in my test like this:

 /**
     * Destroy a product.
     *
     * @param Product $product
     * @return \Illuminate\Http\JsonResponse
     */
    public function destroy(Product $product)
    {
        $this->productRepo->delete($product->id);
        
        DD(DB::table('company_products)
            ->select(DB::raw('*'))
            ->get());

        return response()->json(['status' => 'Verwijderd']);
    }

I receive this:

There was 1 failure:

1) Tests\Feature\Product\DeleteProductTest::when_a_product_is_destroyed_linked_companies_will_go_away
Failed asserting that a row in the table [company_products] does not match the attributes {
    "product_id": "1",
    "company_id": "1"
}.

Found: [
    {
        "product_id": "1",
        "company_id": "1"
    }
].

When I do the exact same thing but then in my webapp I receive:

Collection {#531
  #items: []
}

What the hack could be going on here?

0 likes
14 replies
andonovn's avatar

Hardcoding 1 assumes your database is empty at the start of the test. Maybe you forgot to use DatabaseMigrations, DatabaseTransactions; in your Test class?

tykus's avatar

Do you know for sure that the product and company created in that test example each have an id of 1?

Edit @andonovn got there first.

Also, are the quotation m arks necessary? In these types of test, I like to do a pre-assertion to check that the record(s) were created, then act, and finally an assertion to see that they were destroyed:

public function when_a_product_is_destroyed_linked_companies_will_go_away()
{
    $product = create(Product::class);
    $company = create(Company::class)

    $company->syncProducts([$product->toArray()]);

    $this->assertDatabaseHas('company_products', [
        'product_id' => $product->id,
        'company_id' => $company->id,
    ]);

    $this->actingAs($this->user)->deleteJson('/producten/'.$product->id);

    $this->assertDatabaseMissing('company_products', [
        'product_id' => $product->id,
        'company_id' => $company->id,
    ]);
}
Lars-Janssen's avatar

@tykus @andonovn I use DatabaseMigrations. When I change my test to your example @tykus I receive the same failure.

I've checked with a dd() both product and company id they are both 1.

So weird -_-

tykus's avatar

OK... does this assertion work?

    $this->assertDatabaseHas('company_products', [
        'product_id' => $product->id,
        'company_id' => $company->id,
    ]);

tykus's avatar

Seems that this line is not working like you expect:

 $this->actingAs($this->user)->deleteJson('/producten/'.$product->id);

Can you assign it to a $response and make assertions based on the expected response? If you're no closer, then try dump($response->getContent()) to see if there is any exception?

Lars-Janssen's avatar

@tykus when I

$response = $this->actingAs($this->user)>deleteJson('/producten/'.$product->id);

dd($response->getContent());

I receive the correct json response:

"{"status":"Verwijderd"}"

So that's correct.

Lars-Janssen's avatar

When I do this:

/**
 * Destroy a product.
 *
 * @param Product $product
 * @return \Illuminate\Http\JsonResponse
 */
public function destroy(Product $product)
{
    $this->productRepo->delete($product->id);

    dd(DB::table('products')
        ->select(DB::raw('*'))
        ->get());

    return response()->json(['status' => 'Verwijderd']);
}

The result in my test is an empty collection so the product is being removed.

When I change the dd to:

 dd(DB::table('company_products')
        ->select(DB::raw('*'))
        ->get());

The test result is:

Illuminate\Support\Collection {#1026
  #items: array:1 [
    0 => {#1028
      +"product_id": "1"
      +"company_id": "1"
    }
  ]
}

That's impossible because I use foreign keys -_-

tykus's avatar
tykus
Best Answer
Level 104

Are you depending on the database to cascade delete? Are you using SQLite for testing? Does SQLite support cascading deletes?

1 like
Lars-Janssen's avatar

@tykus

Thanks for helping by the way.

Pivot migration looks like this:

Schema::create('company_products', function (Blueprint $table) {
    $table->integer('product_id')->unsigned();
    $table->integer('company_id')->unsigned();

    $table->foreign('product_id')
        ->references('id')
        ->on('products')
        ->onDelete('cascade');

    $table->foreign('company_id')
        ->references('id')
        ->on('company')
        ->onDelete('cascade');

    $table->primary(['product_id', 'company_id']);
});

My test env:

 <php>
        <env name="APP_ENV" value="testing"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
Lars-Janssen's avatar

@tykus

Thanks for helping. I've fixed it!!

I had to put this in my AppServiceProvider boot method:

 if(config('database.default') == 'sqlite'){
            $db = app()->make('db');
            $db->connection()->getPdo()->exec("pragma foreign_keys=1");
        }
 public function boot()
    {
        if(config('database.default') == 'sqlite'){
            $db = app()->make('db');
            $db->connection()->getPdo()->exec("pragma foreign_keys=1");
        }
    }

Thanks a LOT!

tykus's avatar

Great! Had just created a new app to test same!

Please or to participate in this conversation.