The catch is that we need to override attributes() method since ActiveRecord by default is getting attributes list from database schema which we’re trying to avoid.
Based on this solution, here is the way to stub AR relations:
$attributes = (new Post)->attributes();
$relations = ['comments', 'author'];
/** @var Post|MockObject $task */
$post = $this->getMockBuilder(Post::class)
->setMethods(['attributes'])
->getMock();
// Stub relations by turn them as an usual model attributes.
$post->method('attributes')
->willReturn(array_merge($attributes, $relations));
// Now we can assign values to relations that in normal
// circumstances impossible due to "read only" access to them.
$post->comments = [new Comment];
$post->author = new Author;
// Assign an ordinary attribute, for an example
$post->title = 'the title of the post';
Instead of hard coded class name, which one is considered a better practice: $post = $this->getMockBuilder(Post::className())
or even: $post = $this->getMockBuilder(Post::class)
Note: This kind of destroys the normal workflow, since validate() is no longer run. The code has to manually run validate() before running save(), which is a bit awkward. Maybe there’s a better way to mock this particular method? Example: