How can I test a Yii2 model in a library project?


(nasragiel) #1

I’m trying to implement an adapter that is using a Yii model object extending yii\db\ActiveRecord . The object is passed as constructor arg to the adapter class.

My issue is now that I still couldn’t figure out how to get this to work properly. I’ve even tried mocking it but got stuck because Yii is using lots of static methods to get it’s objects. Sure, I could now try to mock them… But there must be a better way?

public function testSuccessFullFind(): void
{
    $connection = (new Connection([
            'dsn' => 'sqlite:test'
        ]))
        ->open();

    $queryBuilder = new \yii\db\sqlite\QueryBuilder($connection);

    $app = $this->createMock(Application::class);
    \Yii::$app = $app;

    $app->expects($this->any())
        ->method('getDb')
        ->willReturn($this->returnValue($connection));

    $userModel = new UserModel();
    $resovler = new Yii2Resolver($userModel);
    $result = $resolver->find(['username' => 'test', 'password' => 'test']);
    // TBD Asserts on the $result
}

This results in:

1) Authentication\Test\Identifier\Resolver\Yii2ResolverTest::testSuccessFullFind
Error: Call to a member function getDb() on null

vendor\yiisoft\yii2-dev\framework\db\ActiveRecord.php:135
vendor\yiisoft\yii2-dev\framework\db\ActiveQuery.php:312
vendor\yiisoft\yii2-dev\framework\db\Query.php:237
vendor\yiisoft\yii2-dev\framework\db\ActiveQuery.php:133
tests\TestCase\Identifier\Resolver\Yii2ResolverTest.php:31

The code above is the WIP of a test case.

So how can I configure a test connection and get my ActiveRecord object to use it?


#2

There is no need to mock the application for this, you can pass the connection as the first argument to all().

see https://www.yiiframework.com/doc/api/2.0/yii-db-activequery#all()-detail


(nasragiel) #3

Sorry, my snippet is incomplete or hard to understand if you don’t know what I’m trying to do.

The find is not supposed to stay there. I have this line just there to see if the find works at all. This line is supposed to go into the adapter class I’m writing. Let me update my initial post.

This is going to be a framework agnostic library for authentication with adapters to different frameworks / ORM systems and I want to add one for Yii2 so it works out of the box with Yii2 as well.


#4

To have your adapter independent of Yii application, it could be configured with a DB connection.

This connection could then be used inside the adapter and passed to the all() or one() methods when executing queries. You may use the following pattern that is common in Yii for configuring exchangeable components:


(nasragiel) #5

Yes, thank you, I’ve ended up with this:

<?php
namespace Authentication\Identifier\Resolver;

use Authentication\Identifier\ResolverInterface;
use yii\db\ActiveRecord;
use yii\db\Connection;

/**
 * Yii 2 Active Record Resolver
 */
class Yii2Resolver implements ResolverInterface
{

    protected $userModel;

    protected $connection;

    /**
     * Constructor.
     *
     * @param \yii\db\ActiveRecord
     * @param \yii\db\Connection
     */
    public function __construct(ActiveRecord $userModel, ?Connection $connection)
    {
        $this->userModel = $userModel;
        $this->connection = $connection;
    }

    /**
     * {@inheritDoc}
     */
    public function find(array $conditions, $type = self::TYPE_AND)
    {
        $query = $this->userModel->find();

        foreach ($conditions as $field => $value) {
            //$query->where([
            //]);
        }


        $result = $query->one($this->connection);
        dd($result);

        //$this->userModel->find()->orderBy('name')->all();

        /*
        $user = Yii::app()->db->createCommand()
            ->select('id, username, profile')
            ->from('tbl_user u')
            ->join('tbl_profile p', 'u.id=p.user_id')
            //->where('id=:id', array(':id' => $id))
            ->queryRow();
        */
    }

}

If you have any recommendation how to make this more flexible or easy for users of the Yii framework any idea is welcome. I’m usually not using Yii but want to provide an adapter for this framework as well.


#6

If you follow the pattern shown above using ‘db’ as the default and Instance::ensure will make it easier to configure the class using Yii builtin tools, but it is fine like it’s now as well.