garygreen's avatar

Do you unit test model factories? (and their states)

Let's say your using factory(Post::class)->make() to create a Post model in your unit tests, and a post can have various states e.g. published, unpublished. By default, a post will return unpublished.

Do you write unit tests to ensure that the factory is returning the default state correctly for use in your tests? I would assume it's a good idea because if a developer on your team comes along and updates your Post factory to seed/add more data they could give false positives or other problems in your tests that assume a post is unpublished by default, or that a published post is actually marked as published. Or is this just an unnecessary concern?

0 likes
3 replies
tykus's avatar

Or is this just an unnecessary concern?

This.

Where would you stop; unit test the unit tests?

garygreen's avatar

But if your unit tests rely on the state's from the model factories, surely it makes sense to test that the model factories are returning the state you would expect?

It seems like it is a pretty valid concern, otherwise if you did start filling more data on a model factory you might involuntarily fail some tests, or pass some tests that shouldn't be passing, and you wouldn't really know why.

garygreen's avatar

This is an example code I'm using to test the states of our Booking model. It only tests the "business" logic that's returned by the state is correct, that way I know I can rely on those states in tests.

If someone on my team or I accidentally changes the state or data inside the booking factory, for example change the appointment date to be one in the past or just don't set it anymore, I know I'll get a failing test and clear reason why. It give's good confidence that I can use these in a known, unit tested state in all of my tests and only override the data as needed to test a specific scenario.

<?php
use Tests\TestCase;
use App\Member\Member;
use App\Booking\Booking;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class BookingModelFactoryTest extends TestCase
{
    use DatabaseTransactions;

    public function test_default_state()
    {
        $booking = factory(Booking::class)->create();

        $this->assertNotNull($booking->member);
        $this->assertNotNull($booking->client);
        $this->assertNotEquals($booking->client_id, $booking->member_id, 'The client and the member must be different.');
        $this->assertTrue($booking->member->isPro(), 'The member must be a pro.');
        $this->assertEquals(80, $booking->hourly_rate, 'The standard hourly rate must be used.');
        $this->assertEquals(2, $booking->hours, 'The standard number of hours must be used.');
        $this->assertNotNull($booking->appointment_at, 'Booking must have a date set.');
        $this->assertTrue($booking->appointment_at->isFuture(), 'Booking must be for a future date.');
        $this->assertEquals($booking->client->getKey(), $booking->created_by, 'Booking must be created/sent by the client.');
    }

    public function test_accepted_state()
    {
        $booking = factory(Booking::class)->states('accepted')->create();

        $this->assertNotNull($booking->accepted_at);
        $this->assertTrue($booking->accepted_at->isPast());
        $this->assertEquals($booking->getOtherMember($booking->createdBy)->getKey(), $booking->accepted_by);
    }
}

Please or to participate in this conversation.