dongxu7408 2018-04-04 11:18
浏览 67
已采纳

PHP - 在其主体中使用unmockable类的单元测试方法(PHPUnit)

I have a method that uses another class to calculate its outcome, that I want to test using PHPUnit.

/**
 * Returns true if the given user has been granted the given permission.
 *
 * @param User $user
 * @param AbstractPermission $permission
 * @return bool
 */
public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
{
    // Retrieve model from database.
    $user_permission = UserPermission::scopeUser($user)
        ->scopePermission($permission)
        ->first();

    return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
}

Leaving out of consideration what this method actually does, I am wondering how to test this method. I can pass mocks of the User and AbstractPermission classes to the method, but the UserPermission class that is used inside the method's body (to retrieve a model from the database) I can do nothing with.

On top of that, if I pass mocks of the User and Permisson classes, they won't exist in the database, so when UserPermission queries the database, it will receive no results and the method will fail.

What do I do here? Is it considered good practice to simply mock the database (i.e. copying the live db structure and filling it with test data) and let my model query that database, and just trusting that everything is OK? Any suggestions on what to do here?

On a side note, UserPermission is an Eloquent model. I am merely making use of Eloquent here - without Laravel.

  • 写回答

2条回答 默认 最新

  • doubingqi5829 2018-04-04 14:39
    关注

    As a general rule, you can't directly mock static methods - at least, there's no good way to do it. Depending on how your application is set up, you might be able to hack something together that involves redefining the method with runkit or perhaps messing with includes/autoloader to load a mock class instead of the real one, but such solutions are kludgey at best.

    One simple approach to allow unit-testing would be to wrap your static method calls in an instance method. So you'd create a new class with instance methods that call the static methods. Of course, you wouldn't be able to test that new class, but if it's a thin wrapper around the static methods then there's not really any value in testing it anyway.

    So you might end up with something like this, for example:

    class UserPermissionWrapper {
        public function getUserPermission($user) {
            return UserPermission::scopeUser($user);
        }
    }
    

    Then you can inject that into your original class and get something like this:

    public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
    {
        // Assume this is an instance of UserPermissionWrapper injected at construction
        $user_permission = $this->userPermissionWrapper
            ->getUserPermission($user)
            ->scopePermission($permission)
            ->first();
    
        return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
    }
    

    Now you have an object calling instance methods, so you can inject a mock version of that class and set up the method calls in the normal way.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 优质github账号直接兑换rmb,感兴趣伙伴可以私信
  • ¥15 错误(10048): “调用exui内部功能”库命令的参数“参数4”不能接受空数据。怎么解决啊
  • ¥15 安装svn网络有问题怎么办
  • ¥15 Python爬取指定微博话题下的内容,保存为txt
  • ¥15 vue2登录调用后端接口如何实现
  • ¥65 永磁型步进电机PID算法
  • ¥15 sqlite 附加(attach database)加密数据库时,返回26是什么原因呢?
  • ¥88 找成都本地经验丰富懂小程序开发的技术大咖
  • ¥15 如何处理复杂数据表格的除法运算
  • ¥15 如何用stc8h1k08的片子做485数据透传的功能?(关键词-串口)