Welcome Guest, Not a member yet? Register   Sign In
How do I mock Models when testing?
#1

(This post was last modified: 06-17-2022, 03:51 AM by tgix.)

Trying to add tests to my CI4 library but I cannot understand how to mock Models. 
I want to test my CredlyProvider library. This library contains code that use Tangix\VirtualTester\Models\UserModel to provide all the logic needed to lookup a user's information:
PHP Code:
        $user UserModel::factory()
            ->find($userID); 

For testing purposes, using the full UserModel would require tons of database tables and data to be seeded and is not viable situation.
Instead I planned to create a simple substitute Model for testing simply return a static known data that can be used.
I put this in ./tests/_support/Mocks/UserModel.php:

PHP Code:
<?php

namespace Tests\Support\Mocks;

use 
Tangix\VirtualTester\Entities\UserEntity;
use 
CodeIgniter\Model;

class 
UserModel extends Model
{
    public static function factory(): UserModel
    
{
        return new UserModel();
    }

    public function find($id null)
    {
        $res                  = new UserEntity();
        $res->firstname      'Mattias';
        $res->lastname        'Sandström';
        $res->email          '[email protected]';
        $res->userID          28;
        $res->companyLink    11;
        $res->badge_earner_id '1ce8fbcd-e529-4fa2-bf58-4c424804204e';

        return $res;
    }

    public function update($id null$data null): bool
    
{
        return true;
    }


And then trying to run the following in the test:

PHP Code:
    public function test05IssueBadge()
    {
        $model = \Tests\Support\Mocks\UserModel::factory();
        \CodeIgniter\Config\Factories::injectMock('models'Tangix\VirtualTester\Models\UserModel::class, $model);

        $obj = new \Tangix\VirtualTester\Libraries\CredlyProvider(3);
        $result $obj->issue_badge('656a7a19-e86f-4826-9221-31b9dd15b7ff'280''true);
        $this->assertTrue($result);
    

Unfortunately this doesn't work, the Tangix\VirtualTester\Models\UserModel is used instead of the mocked one.
I have tried moving the injectMock() call to setUp() and other places in the test file, but so far no go.
Namespaces seems to be setup correct because $model contains the mocked version.

Could anyone share some info on how they go about testing Libraries with mocked Models?

Thanks,
/Mattias
Reply
#2

Your mocking approach generally looks good to me. The issue is that you aren’t using Factories for your instantiation. Instead of `return new UserModel();` you should get the shared instance from factories, e.g. with the helper method:
return model(self::class);

Factories is intended to be a central factory manager so having UserModel::factory() is a bit redundant. I would normally handle your original call like this:
$user = model(UserModel::class)->find($userID);

If you don’t like the helper method and would prefer to be explicit about the fact that this is a factory then you can use the class directly:
Factories::model(UserModel::class)
Reply
#3

Thanks MGatner - that works!
Never used the model() call, that's why I "invented" my own factory(), but I guess I should rethink that approach now. Only object would be that it looks a bit ugly and redundant use of "model":
PHP Code:
$user model(UserModel::class)->find(28); 
Renaming all the models perhaps.... ugh...
Thanks again!
Reply
#4

(This post was last modified: 06-19-2022, 03:24 AM by MGatner.)

I like your model factory approach! You could keep using it that way and even expand it by placing it on a parent BaseModel and have it do something like this:


PHP Code:
public static function factory(): static
{
    return model(static::class);



That way you could always access your shared factory instance but still use the actual model class you are after:

PHP Code:
UserModel::factory()->find(1); 
Reply
#5

(06-19-2022, 03:22 AM)MGatner Wrote: I like your model factory approach! You could keep using it that way and even expand it by placing it on a parent BaseModel and have it do something like this:


PHP Code:
public static function factory(): static
{
    return model(static::class);



That way you could always access your shared factory instance but still use the actual model class you are after:

PHP Code:
UserModel::factory()->find(1); 

Thanks, I actually already did exactly that - I don't compare myself with giants like yourself, but great minds think alike ;-)
This was a good refresher in self, static and parent BTW...
Reply




Theme © iAndrew 2016 - Forum software by © MyBB