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条)

报告相同问题?

悬赏问题

  • ¥30 eclipse开启服务后,网页无法打开
  • ¥30 雷达辐射源信号参考模型
  • ¥15 html+css+js如何实现这样子的效果?
  • ¥15 STM32单片机自主设计
  • ¥15 如何在node.js中或者java中给wav格式的音频编码成sil格式呢
  • ¥15 不小心不正规的开发公司导致不给我们y码,
  • ¥15 我的代码无法在vc++中运行呀,错误很多
  • ¥50 求一个win系统下运行的可自动抓取arm64架构deb安装包和其依赖包的软件。
  • ¥60 fail to initialize keyboard hotkeys through kernel.0000000000
  • ¥30 ppOCRLabel导出识别结果失败