在Yii应用程序中使用Restful Api的TDD最佳实践

时间:2013-01-22 21:46:29

标签: api unit-testing yii tdd phpunit

我一直在寻找在Yii app开发中使用TDD的最佳方式。现在,大多数Web应用程序都由前端API层(通常是JSON)组成,以向服务器和后端提供异步调用。就我而言,应用程序中最常用的测试是单元测试和功能测试。后者在指南和书籍中最广泛地展示了利用PHPUnit + Selenium,但Behat + Mink看起来也很酷(但我对此还不是很有信心)

如果你曾经使用过使用浏览器的功能测试(比如Selenium),那么你知道你运行它们越少,你感觉越好。这导致它们变得更慢,更难维护,有时(如使用JS SDK的弹出式FB登录)很痛苦。

使用单个网页应用程序时,我关心测试我的apis的JSON输出。我想用单元测试方法来测试这些功能,以便更快地进行更容易维护的测试。考虑到我的Controller的大部分操作都是使用accessControl过滤器的Logged only用户可用的,我想知道如何让我的测试运行起来的最佳方法。

此时我想有两种方法可以实现这个目标

  • 将cUrl用于所需的enpoint以直接调用JSON
  • 控制器的功能

在第一个场景中,我可以使用fixture,但我无法模拟CWebUser类(模拟已记录的用户),当cUrl出现时,使用Apache由我的CWebApplication实例执行,该实例不是由执行者执行的PHPUnit的。我可以通过使所有API调用无状态来解决这个问题,从而删除accessControl过滤器。

在第二个中,我发现模拟CWebUser类的唯一方法是在我正在执行的测试类中覆盖它。这种方法支付直到我不需要测试需要不同类型用户的用例,我无法在运行时(或在设置时)更改我的webuser模拟。我发现模仿我的webuser的唯一方法是你可以在下面找到的,这导致$ this-> getMock('WebUser')不会影响配置文件中定义的CWebApplication的WebUser(只读)单例。

这是一个具体的例子:

class UserControllerTest extends CDbTestCase
{
    public $fixtures=array(
            /* NEEDED FIXTURES*/
    );

    public function testUserCanGetFavouriteStore() {

            $controller = new UserController(1);
            $result = json_decode($controller->actionAjaxGetFavStore());                    
            $this->assertInternalType('array', $result->data);              

            $model  = $result->data[0];
            $this->assertEquals($model->name, "Nome dello Store");  

    }
}

class WebUser extends CWebUser {

    public function getId() {
        return 1;
    }
    public function getIsGuest() {
            return false;
    }
};

我想知道是否可以通过API密钥或用户/密码组合使用api接口进行身份验证可能很有用。 如果我转向几乎无状态的API集成,这是可以的,但大多数时候我只有控制器的操作(仅允许登录用户)返回Json数据以填充前端。

任何人都可以建议我一个更好的方法吗?也许测试这种JSON输出是没用的?

最好的问候

2 个答案:

答案 0 :(得分:0)

也许我过分简化了你的问题。听起来你想在运行测试之前模拟用户登录?如果是这种情况,为什么不在夹具中创建一个User对象并在运行测试之前实际登录它们,然后将它们记录下来?

类似的东西:

/**
 * Sets up before each test method runs.
 * This mainly sets the base URL for the test application.
 */
protected function setUp()
{
    parent::setUp();

    // login as registered user
    $loginForm = new UserLoginForm();
    $loginForm->email = USER_EMAIL; // use your fixture
    $loginForm->password = USER_PASSWORD; // use your fixture
    if(!$loginForm->login()) {
        throw new Exception("Could not login in setup");
    }
}

protected function tearDown()
{
    parent::tearDown();
    Yii::app()->user->logout(true);
}

答案 1 :(得分:0)

确实,我和我的团队发现的唯一解决方案是创建一个存根WebUser类。 以这种方式重写WebUser类,您可以在不让Yii依赖会话的情况下对用户进行身份验证。

class WebUserMock extends WebUser {

public function login($identity,$duration=0)
{
    $id=$identity->getId();
    $states=$identity->getPersistentStates();
    if($this->beforeLogin($id,$states,false))
    {
        $this->changeIdentity($id,$identity->getName(),$states);
        $duration = 0;
        if($duration>0)
        {
            if($this->allowAutoLogin)
                $this->saveToCookie($duration);
            else
                throw new CException(Yii::t('yii','{class}.allowAutoLogin must be set true in order to use cookie-based authentication.',
                    array('{class}'=>get_class($this))));
        }

        $this->afterLogin(false);
    }
    return !$this->getIsGuest();
}

public function changeIdentity($id,$name,$states)
{   
    $this->setId($id);
    $this->setName($name);
    $this->loadIdentityStates($states);
}

// Load user model.
protected function loadUser() {
    $id = Yii::app()->user->id;
        if ($id!==null)
            $this->_model=User::model()->findByPk($id);
    return $this->_model;
}
};

在测试类的setUp方法中,您可以登录任何用户(在这种情况下利用我的灯具)

//a piece of your setUp() method....
$identity = new UserIdentity($this->users('User_2')->email, md5('demo'));               
$identity->authenticate();      
if($identity->errorCode===UserIdentity::ERROR_NONE)                                     
    Yii::app()->user->login($identity);             

最后要做的事情是覆盖测试配置文件中的用户组件并告诉他使用这个:

保护/配置/ test.php的

'user'=>array(
    'class' => 'application.tests.mock.WebUserMock',
    'allowAutoLogin'=>false,
), 

仍然不确定这是处理它的最好方法,但似乎工作正常

相关问题