TDD Best Practice of Using Resty Api in Yii Application

I am constantly looking for the best way to use TDD in Yii application development. Most web applications nowadays are composed by an API layer front (usually JSON) to provide asynchronous calls to the server and backend. For my part, most of the tests used in this application are unit and functional. The latter is most widely shown in tutorials and books using PHPUnit + Selenium, but Behat + Mink is pretty cool too (but I'm not sure about that yet)

If you've ever used functional tests that use a browser (like Selenium), you know that the less you have to run them, the better you feel. This is because they are slower, harder to maintain, and sometimes (like the FB Login popup using the JS SDK) are painful.

When working with a single web page, I care about testing the JSON output of my apis. I would like to test these features using a unit test approach in order to have faster tests that are easier to maintain. Considering that most of my controller actions are only available to the user for login using the accessControl filter, I wondered about the best ways to run and run my tests.

At this point, I think I have two ways to accomplish this

  • use cUrl towards your desired enpoint to call JSON directly.
  • controller function

In the first scenario, I can use appliances, but I had no way to mock the CWebUser class (to emulate a logged in user) using Apache, when cUrl comes in, it is started by an instance of my CWebApplication, which is not an executable PHPUnit. I can get rid of this problem by making all my API calls stateless and, as a consequence, removing the accessControl filter.

In the second case, the only way I have found to mock the CWebUser class is to override it in the test class I am executing. This approach pays for as long as I donโ€™t need to validate use cases that require a different user type, and I was not able to change it at runtime (or during installation) of my web user manual. The only way I have found to mock my web user is the one you can find below, this reason $ this-> getMock ('WebUser') does not affect any singleton CWebApplication WebUser (read-only). defined in the config file.

Here's a specific example:

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;
    }
};

      

I was wondering if the ability to authenticate with an api might be useful either with an API key or with a user / password combination. It's ok if I go to integrate with a near-stateless API, but most of the time I only have controller actions (only allowed for login) that returns Json data to populate the interface.

Can anyone suggest a better method for me? Maybe it's just useless to test this JSON output?

Regards

+3


source to share


2 answers


I may be oversimplifying your problem. Sounds like you want to emulate user logins before running the test? If so, why not just create a User object in your instrument and actually register them before running the test and log them out after?

Something like:



/**
 * 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);
}

      

0


source


Ok, this is actually the only solution that my team and I have created to create a WebUser stub class. By rewriting the WebUser class this way, you can authenticate the user without relying on the Yii session.

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;
}
};

      

In the setUp method of your test class, you can login with any user (in this case using my fixtures)

//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);             

      



As the last thing to do, just override the custom component in your test config file and tell it to use this:

protected / config / test.php

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

      

Still not sure if this is the best way to handle this, but seems to work fine

0


source







All Articles