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 服务端控制goose报文控制块的发布问题
  • ¥15 学习指导与未来导向啊
  • ¥15 求多普勒频移瞬时表达式
  • ¥15 如果要做一个老年人平板有哪些需求
  • ¥15 k8s生产配置推荐配置及部署方案
  • ¥15 matlab提取运动物体的坐标
  • ¥15 人大金仓下载,有人知道怎么解决吗
  • ¥15 一个小问题,本人刚入门,哪位可以help
  • ¥30 python安卓开发
  • ¥15 使用R语言GD包一直不出结果