Summer Sale! All accounts are 50% off this week.

imirko's avatar
Level 15

assertSee on a non existing property returns green

Hello everyone,

I have a couple of years of experience with Laravel but I am completely new to TDD world. Just learning and Laracasts has been a great resource, as always.

I found one strange behavior with assertSee method when following the step-by-step TDD approach so I was wondering if this was normal.

I have a simple test for viewing a listing of messages:

/** @test */
public function viewing_all_messages()
{
    $message = factory('App\Message')->create();

    $this->get('/messages')
        ->assertSee($message->title);
}

A brand new factory is in place, no fields defined. Model is also generated, fresh. Migration is there, no fields added (id, created_at and updated_at is all there is), I have the route defined to point to MessagesController@index. In controller I have just a simple few lines:

public function index()
{
    $messages = Message::all();

    return view('messages.index', compact('messages'));
}

Nothing fancy there.

I have the view created but nothing inside. Blank file completely.

Until this point I followed the TDD approach by solving one error at a time and running the test after each solved to get to the next one. But here when I created the view before writing anything inside the file, to my surprise, the test passed. I expected to see something like: title property does not exist on the message object or so. After some debugging and running through the code I understood that non existing property that was put there as part of programming by wishful thinking (which is the message title since it is not defined in migration nor the factory) is actually null. And since the assertSee method behind the scenes converts the passed value to string before comparing it to the view contents (converting null to string in PHP returns an empty string "") it makes the test pass because the contents of my view is also an empty string. But, at least to me, this looks like the test is wrongly passing.

I have seen @JeffreyWay using this approach before in his tests (Let's Build a Forum with Laravel and TDD series for example), so I was wondering if it is legitimate to have it passing at this point.

What do you think?

0 likes
6 replies
tykus's avatar

I don't think I have tried this as I'd usually populate something in the factory immediately. However, t it might simply be that you are asserting that null (the message title) is the same as an empty string (the document).

imirko's avatar
Level 15

Hey @tykus, thanks for the reply.

From what I have found that is the case in the end, but shouldn't an error be thrown because title doesn't exist on the message? It is not defined anywhere at all.

This is how the migration looks like:

public function up()
{
    Schema::create('messages', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
    });
}

And heres the factory:

$factory->define(App\Message::class, function (Faker $faker) {
    return [
        //
    ];
});

The question is why is it null?

tykus's avatar
tykus
Best Answer
Level 104

You can call any property on an Eloquent model and will get null unless it has an accessor (virtual/computed), a property on the Model instance, a column on the table or a relation result.

If you source-dive the Model class (and its HasAttributes trait), you will see there is a magic __get method which accepts the property name you are trying to get and attempts to get a value for that property from the model in the order described above. If none is found, null is returned as the result for that property.

imirko's avatar
Level 15

I caught some time and explored the Model class and HasAttributes trait, you were absolutely right. The last method in the chain ends with a null because none of its conditions are met.

I wonder if this behavior has any practical purpose? From my point of view it can be misleading and in some cases maybe even dangerous by being too forgiving on you when calling non-existing attributes on the model. In this example it totally breaks the assertSee() method in tests.

LIpeTuga's avatar

Hello,

Next time try assert the status (assertStatus()), before the assertSee.

CLab's avatar

A workaround for this issue is something like

$response ->assertSeeText($message->title ?? 'No title field')

This part $message->title ?? 'No title field' checks if there is a $message->title and if not returns No title field - as long as your view does not show this text it will fail.

Please or to participate in this conversation.