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 网络科学导论,网络控制
  • ¥100 安卓tv程序连接SQLSERVER2008问题
  • ¥15 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同
  • ¥50 如何openEuler 22.03上安装配置drbd
  • ¥20 ING91680C BLE5.3 芯片怎么实现串口收发数据