Reputation: 66500
I have an Laravel User
model based on the Illuminate\Database\Eloquent\Model
.
It's used within a service that I want to unit test. That's why I want to mock the User
with certain properties, in this case it's about the id
which should be set to 23
.
I create my mock via:
/**
* @param int $id
* @return User|\PHPUnit\Framework\MockObject\MockObject
*/
protected function createMockUser(int $id): \PHPUnit\Framework\MockObject\MockObject|User
{
$user = $this
->getMockBuilder(User::class)
->disableOriginalConstructor()
->getMock();
$user->id = $id;
return $user;
}
I also tried:
$user->setAttribute('id', $id);
But in both cases, the id
property on the mock will be null
.
How do I set a property on a Laravel model's mock?
Upvotes: 3
Views: 8532
Reputation: 603
While I agree with @mrhn that mocking Eloquent magic can get complicated quickly, and "you have to mock unnecessary long call chains", there's still value in having that ability. Testing everything with the Laravel app and container can become time consuming to run, and eventually less useful in a dev environment as that becomes a significant disincentive.
This answer regarding magic methods is the actual how to the OP. So your $user
mock could look like this:
$user = $this
->getMockBuilder(User::class)
->disableOriginalConstructor()
->setMethods(['__get'])
->getMock()
;
$user->expects($this->any()
->method('__get')
->with('id')
->willReturn($id)
;
Upvotes: 2
Reputation: 18926
As stated in my comment, you should not Mock users. Laravel
has multiple tools to combat this, main reason is you have to mock unnecessary long call chains, static functions and partial mocking won't work with table naming and save operations. Laravel
has great testing documentation eg. testing with a database, which answers most of the best practices with testing in Laravel
.
There is two approaches from here, you can create factories that can create users that are never save in the database. Calling create()
on a factory will save it to the database, but calling make()
will fill out the model with data and not saving it.
class UserFactory extends Factory
{
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
];
}
}
// id is normally set by database, set it to something.
$user = User::factory()->make(['id' => 42]);
Instead with both unit testing and more feature oriented testing. It is way easier to just default to using sqlite
both for performance and ease of use in a testing environment.
Add the following.
config/database.php
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => ':memory:',
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
phpunit.xml
<server name="DB_CONNECTION" value="sqlite"/>
In your test use this trait.
use RefreshDatabase;
Now you should able to use the following. This will provide a complete User model, with all the bell and whistles you needs. The only downside with sqlite
is due to which version you use, foreign keys and weird database specific features are not supported.
public function test_your_service()
{
$result = resolve(YourService::class)->saveUserData(User::factory()->create());
// assert
}
Upvotes: 4