如何通过Symfony中的PHPunit模拟一个函数来测试持久化到数据库

时间:2016-04-06 02:48:08

标签: symfony mocking phpunit

这是第一次通过PHPunit创建单元测试来测试我的Web应用程序的业务模型。我很困惑嘲笑用来坚持实体的我的类。这是newUser()的功能。

class AccountBM extends AbstractBusinessModel {

    public function newUser(Account $account) {
        $salt = StringHelper::generateRandomString ( "10" );
        $account->setUsername ( $account->getEmail () );
        $invitation_code = hash ( 'md5', $account->getEmail () );
        $account->setInviationCode ( $invitation_code );
        $account->setPassword ( $this->encoder->encodePassword ( $account->getPassword (), $salt ) );
        $account->setSalt ( $salt );
        $this->persistEntity ( $account );
        return $account;
    }
}

然后,我尝试为这个功能testNewUser()

创建一个测试单元
 public function testNewUser() {
        $account = $this->newAccountEntity ();

        $account_bm =$this->getMockBuilder('AccountBM')
                    ->setMethods(array('newUser'))
                    ->disableOriginalConstructor()
                    ->getMock();
        $account=$account_bm->newUser($account);

        // compare setter value with saved entity
        $this->assertEquals ( 'test@test.com', $account->getEmail () );
        $this->assertEquals ( '123456789', $account->getPhone () );
        $this->assertEquals ( '987654321', $account->getMobilePhone () );
        $this->assertEquals ( 'Mr', $account->getTitle () );
        $this->assertEquals ( 'test', $account->getFirstName () );
        $this->assertEquals ( 'test', $account->getLastName () );
        $this->assertEquals ( 'male', $account->getGender () );
        $this->assertEquals ( '1', $account->getCompanyLogo () );
        $this->assertEquals ( AccountType::IDS, $account->getUserType () );
        $this->assertEquals ( RegionType::NCN, $account->getRegion () );
        $this->assertEquals ( hash ( 'md5', $account->getEmail () ), $account->getInviationCode () );
        $this->assertEquals ( hash ( 'sha256', $account->getSalt () . "test" ), $account->getPassword () );
    }

public function newAccountEntity() {
        $account = new Account ();

        // set account
        $account->setEmail ( "test@test.com" );
        $account->setPassword ( "test" );
        $account->setPhone ( "123456789" );
        $account->setMobilePhone ( "987654321" );
        $account->setTitle ( "Mr" );
        $account->setFirstName ( "test" );
        $account->setLastName ( "test" );
        $account->setGender ( "male" );
        $account->setCompanyLogo ( "1" );
        $account->setUserType ( AccountType::IDS );
        $account->setRegion ( RegionType::NCN );
        return $account;
    }

但它似乎不会嘲笑“newUser”这个方法,而不是我的实体。测试代码有什么问题吗?感谢。

1 个答案:

答案 0 :(得分:1)

回答您的问题:

模拟方法不是您要测试的方法,而是您想要测试的方法。在你的代码中,你正在创建一个你想要测试的类的模拟对象(虽然你通常模拟你不想测试的依赖类,但是完全没问题)并告诉模拟对象,newUser()是一个方法你想嘲笑

相反,您想要测试newUser(),并且可能想要测试persistEntity()

$account_bm =$this->getMockBuilder('AccountBM')
            ->setMethods(array('persistEntity'))
            ->disableOriginalConstructor()
            ->getMock();
$account=$account_bm->newUser($account);

您可能还想查看有关模拟对象的PHPUnits文档: https://phpunit.de/manual/current/en/test-doubles.html

其次,使用PHPUnit进行开发的一些技巧:

在你的测试中,你有很多断言。最好的情况是每个测试只有一个断言,PHPUnit实际上可以很好地帮助你,因为它能够比较几乎所有东西。因此,不是断言对象的每个值,而是可以用另一个声明一个对象,如果这些对象不相等,PHPUnit将显示差异。

另外,您甚至可能想在实体中遗漏一些您不需要的信息。您的数据库可能需要名字和姓氏,但您的方法不需要,因此您的测试也不需要。您不必编写那么多代码,而且更具可读性。

但这不是全部,断言也可以用模拟方法完成。

尝试这样的测试:

public function testNewUser() {
    // this is the entity for the method
    $account = new Account();
    $account->setEmail ( "test@test.com" );
    $account->setPassword ( "test" );

    // this is how the entity should look like when it's done
    $expectedAccount = new Account ();
    $expectedAccount ->setEmail ( "test@test.com" );
    $expectedAccount ->setInviationCode ( md5("test@test.com") );
    // changes needed, how does your encoder create the password hash?
    // Or even better: Also mock your encoder, you don't want to test it here
    $expectedAccount ->setPassword ( "test" );
    // As this is a random value, you might want to mock it as well
    // mock a static call
    $expectedAccount ->setSalt ( "10" );

    /** @var AccountBM|\PHPUnit_Framework_MockObject_MockObject $account_bm */
    $account_bm = $this->getMockBuilder('AccountBM')
        ->setMethods(array('persistEntity'))
        ->disableOriginalConstructor()
        ->getMockForAbstractClass();

    $account_bm->expects($this->once())
        ->method("persistEntity")
        ->with($expectedAccount);

    $account_bm->newUser($account);
}

您现在可以使用$expectedEntity声明返回的实体,但我离开时向您显示,期待一个方法被称为带有特定参数将触发一个断言。

正如您将看到的,上述测试将失败并显示哪些属性是错误的。为什么会这样?

有两个ToDo标记:您的两个值是由类外的方法生成的,应该被模拟。这些是$this->encoder->encodePassword()StringHelper::generateRandomString()

因此,为encoder创建一个模拟对象,并模拟一个静态方法,将其放在AccountBM中的单独方法中以包装它:

<强> AccountBm

public function newUser(Account $account) {
    $salt = $this->generateSalt("10");
    $account->setUsername ( $account->getEmail () );
    $invitation_code = hash ( 'md5', $account->getEmail () );
    $account->setInviationCode ( $invitation_code );
    $account->setPassword ( $this->encoder->encodePassword ( $account->getPassword (), $salt ) );
    $account->setSalt ( $salt );
    $this->persistEntity ( $account );
    return $account;
}

public function generateSalt($string)
{
    return StringHelper::generateRandomString ( $string );
}

现在和encoder

一起模仿这个方法
public function testNewUser() {
    $account = new Account();
    $account->setEmail ( "test@test.com" );
    $account->setPassword ( "test" );

    $expectedAccount = new Account ();
    $expectedAccount ->setEmail ( "test@test.com" );
    $expectedAccount ->setInviationCode ( md5("test@test.com") );
    $expectedAccount ->setPassword ( "test" );
    $expectedAccount ->setSalt ( "10" );

    /** @var AccountBM|\PHPUnit_Framework_MockObject_MockObject $account_bm */
    $account_bm = $this->getMockBuilder('AccountBM')
        ->setMethods(array('persistEntity', 'generateSalt'))
        ->disableOriginalConstructor()
        ->getMockForAbstractClass();

    // mock the wrapper for the static method
    $account_bm->expects($this->once())
        ->method("generateSalt")
        ->with("10")
        ->will($this->returnValue("10"));

    // mock the encoder and set it to your object
    $encoder = $this->getMockBuilder("stdClass")
        ->setMethods(array("encodePassword"))
        ->disableOriginalConstructor()
        ->getMock();
    $encoder->expects($this->once())
        ->method("encodePassword")
        ->with("test", "10")
        ->will($this->returnValue(md5("test10")));
    $account_bm->setEncoder($encoder);

    $account_bm->expects($this->once())
        ->method("persistEntity")
        ->with($expectedAccount);

    $account_bm->newUser($account);
}

但是测试仍然失败 - 我会留给你更换错误的值来检查你是否理解了代码是如何工作的; - )