【问题标题】:Mocking Laravel Eloquent models - how to set a public property with MockeryMocking Laravel Eloquent 模型 - 如何使用 Mockery 设置公共属性
【发布时间】:2014-05-19 12:08:17
【问题描述】:

我想在我的 PHPUnit 测试中使用模拟对象 (Mockery)。模拟对象需要同时设置一些公共方法和一些公共属性。该类是 Laravel Eloquent 模型。我试过这个:

$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
$mock->roles = 2; //how to do this? currently returns an error
$this->assertTrue(someTest($mock));

...但设置公共属性返回此错误:

BadMethodCallException:此模拟对象上不存在方法 Mockery_0_User::setAttribute()

模拟简单类时不会返回此错误,但当我尝试模拟 Eloquent 模型时会返回此错误。我做错了什么?

【问题讨论】:

  • 也许是一个愚蠢的问题——但根据你测试的内容,你甚至需要一个模拟对象吗?如果您正在测试(比如说)hasRole() 方法,为什么不使用 $mock = new User; $模拟->角色= 2;并测试一下?例如 - 如果我正在测试访问器/演示者等,我将只是一个适当的对象而不是模拟 setAttribute 等。
  • @Apemantus 谢谢。我正在测试一个有关系的模型(一个用户有很多角色)。我不知道在不保存到数据库的情况下设置关系的方法。我正在尝试编写一个不需要接触数据库的测试。
  • 好的。我没有测试我们的关系,但可以做 $mock->shouldReceive('setAttribute')->with('roles)->andReturn(2)- 但这只会返回整数。如果你想我猜你可以返回一个角色模型的第二个模拟,比如stackoverflow.com/questions/20361364/…

标签: php laravel eloquent mockery


【解决方案1】:

如果你想用这个值获取这个属性,只需使用它:

$mock->shouldReceive('getAttribute')
    ->with('role')
    ->andReturn(2);

如果你打电话给$user->role,你会得到 - 2$user - 它的 User 模拟类)

【讨论】:

  • 我得到了未定义的属性:Mockery\CompositeExpectation::$role
  • 由于公共静态方法执行alias mock 时,我得到了类似的ErrorException: Undefined property: App\User::$role。在这种情况下,我的一个解决方案是直接设置道具,如$mock->role = 2。 (php 7.1, PHPUnit 6.5.14, Mockery 1.2.2, laravel 测试用例Illuminate\Foundation\Testing\TestCase)
【解决方案2】:

这个答案有点晚了,但希望它会对某人有所帮助。您目前可以使用 'alias' 关键字在模拟的 Eloquent 对象上设置静态属性:

$mocked_model = Mockery::mock('alias:Namespace\For\Model');
$mocked_model->foo = 'bar';
$this->assertEquals('bar', $mocked_model->foo);

这对于模拟外部供应商类(如某些 Stripe 对象)也很有帮助。

了解“别名”和“重载”关键字: http://docs.mockery.io/en/latest/reference/startup_methods.html

【讨论】:

  • 我收到错误“无法加载模拟 App\Models\User,类已存在”。
【解决方案3】:

要回答您的问题,您也可以尝试以下方法:

$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
$mock->shouldReceive('setAttribute')->passthru();
$mock->roles = 2; 

$mock->shouldReceive('getAttribute')->passthru();
$this->assertEquals(2, $mock->roles);

或者,按照 seblaze 的建议,使用部分模拟:

$mock = Mockery::mock('User[hasRole]');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2; 
$this->assertEquals(2, $mock->roles);

但是,从你的代码 sn-p 来看,如果你正在编写单元测试,你真的应该在每个测试中只做一个断言:

function test_someFunctionWhichCallsHasRole_CallsHasRole() {
    $mock = Mockery::mock('User');
    $mock->shouldReceive('hasRole')->once();

    $mock->someFunctionWhichCallsHasRole();
}

function test_someFunctionWhichCallsHasRole_hasRoleReturnsTrue_ReturnsTrue() {
    $mock = Mockery::mock('User');
    $mock->shouldReceive('hasRole')->once()->andReturn(true);

    $result = $mock->someFunctionWhichCallsHasRole();

    $this->assertTrue($result);
}        

【讨论】:

    【解决方案4】:

    间谍是你的朋友:

    $mock = Mockery::spy('User');
    $mock->shouldReceive('hasRole')->once()->andReturn(true);
    $mock->roles = 2;
    $this->assertTrue(someTest($mock));
    

    【讨论】:

      【解决方案5】:

      试过了吗?它应该涵盖您的问题。

      https://github.com/padraic/mockery/blob/master/docs/11-MOCKING-PUBLIC-PROPERTIES.md

      我会说实现这些

      protected $roles = array();
      
      public function setRoles($roles)
      {
          $this->roles = $roles;
      }
      
      public function addRole($role)
      {
          $this->roles[] = $role;
      }
      

      然后您可以使用以下方法进行测试:

      $mock = Mockery::mock('User');
      $mock->shouldReceive('hasRole')->once()->andReturn(true);
      $mock->addRole(2);
      $this->assertTrue(someTest($mock));
      

      这个后殿让您有机会在执行 getRoles() 时承诺一种格式,如果您执行 SOLID OOP,或者如果您更愿意使用数组,那么该格式将是 Role 对象的数组,那么至少您知道它始终是一个数组得到。

      【讨论】:

      • 是的,我已阅读此页面,但未提供示例。当我尝试 set() 时,我得到 BadMethodCallException: Method Mockery_0_User::set() 在这个模拟对象上不存在。我真正想要的是一个例子。
      • BadMethodCallException:此模拟对象上不存在方法 Mockery_0_User::setAttribute()
      • 更新了我的回复,当您使用用户对象时,它也会在您的 IDE 中为您提供完整的代码
      • 谢谢,但这不只是改变 User 类的实现来添加 getter 和 setter 吗?虽然这可能是一种解决方法,但我看不出它如何回答原始问题。
      • 知道这是来自 Lavarel 的持久性对象可能很高兴知道:> 现在根据 Mockery 可以注入公共变量,但它不支持魔术函数,我不太了解Lavarel 说这里的问题是什么。它看起来像 ->roles 被转换为 ->setAttribute('roles', $value);但我可能是错的。
      【解决方案6】:

      你试过partial mocks吗? 您可以尝试类似(未测试):

      $mock = Mockery::mock('User[hasRole]');
      $mock->shouldReceive('hasRole')->once()->andReturn(true);
      $mock->roles = 2; 
      $this->assertTrue(someTest($mock));
      

      【讨论】:

        【解决方案7】:

        Laravel 5.3 中的部分模拟

        $mock = Mockery::mock(App\User::class)->makePartial();
        $mock->shouldReceive('hasRole')->once()->andReturn(true);
        $mock->roles = 2; 
        $this->assertEquals(2, $mock->roles);
        

        【讨论】:

          【解决方案8】:

          在这种情况下,您可以使用 stdclass。

          $obj = new stdClass();
          $obj->roles = 2;
          $mock = Mockery::mock('User');
          // return stdclass here.
          $mock->shouldReceive('hasRole')->once()->andReturn($obj);
          $mock->roles = 2;
          $this->assertEquals(2, $mock->roles);
          

          【讨论】:

          • 不,如果您的对象需要该模型类的实例,则不能。
          猜你喜欢
          • 2016-11-26
          • 2014-11-05
          • 2016-08-17
          • 2018-09-04
          • 2015-08-14
          • 2013-09-15
          • 2014-06-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多