Skip to content
Advertisement

How to test create model service?

I decided to create CreateClassroomService to separte logic in my controller method.

class CreateClassroomService extends Service
{
    public function create(string $name, User $user): ?Classroom
    {
        $this->checkName($name);

        $classroom = new Classroom();
        $created = $classroom->setName($name)
            ->associateUser($user)
            ->save();

        return $created ? $classroom : null;
    }

    private function checkName(string $name): void
    {
        if (empty($name)) {
            throw new InvalidArgumentException();
        }
    }
}

I am trying to test this service as part of learning unit testing, but I don’t know how. I don’t know how to mock the classroom object to control what the method should return. Does this mean that creating this service was not a good idea because I am not able to test it? Should I build this service differently? Unless the service can be tested, but I don’t know how… What should I check in assertion?

This is my test but it is not good because I am not able to force what should be returned.

public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
    {

        $name = 'Test classroom';
        $user = Mockery::mock(User::class);


        $result = $this->service->create($name, $user);

        $this->assertTrue($result);

    }

Advertisement

Answer

Not sure how mocking works in Mockery, but lets review what you are doing: your create function that you are trying to test creates a new instance of a Classroom, and since it instantiates the object inline you have no control over it.

From the code you have written, I am assuming that you build a classroom object and return, never using the same creator instance again, so what you can do is add a constructor to CreateClassroomService through which you inject a Classroom object. If you are using the same create service to create multiple classroom objects at a time, you will also need to make sure that you somehow reset the classroom instance to its default state inbetween creation(s), or you inject a fresh & new classroom object before invoking create again. This wholly depends on what the classroom does though.

The classroom object then can be mocked through unit testing — you inject the mock instance and youre good to go. You actually are already injecting User object, so you’re already on the right lines!

class CreateClassroomService extends Service {

private ?Classroom $classroom;

public function __construct(Classroom $classroom) {
    $this->classroom = $classroom;
}

...
}

Now you just need to mock your classroom object in your test, inject the mock into your service when instantiating the object, and off you go. 🙂

Btw I would also say you may want to consider do some more abstraction on your service in the form of interfaces or review your parent class to make it more unit testable in general. Generally speaking for effective unit testing you want to avoid static methods and new keywords; where you can absolutely not avoid it, one approach might be to wrap just that one line of code in an encapsulated method, so you can mock the method instead to return you mock data / mock instances instead (but generally if you have to do this it should be an obvious sign to alert you to having sub-par architecture).

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement