Add ATDC course pages
This commit is contained in:
parent
11ae0f9fc7
commit
72c3d50cf7
11 changed files with 2178 additions and 0 deletions
240
source/_pages/atdc/10.md
Normal file
240
source/_pages/atdc/10.md
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
---
|
||||
title: Lesson 10 - Mocking services
|
||||
permalink: /atdc/10-mocking-services
|
||||
---
|
||||
|
||||
{% block head_meta %}
|
||||
<meta name="robots" content="noindex">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
In this final lesson, let's continue looking at unit testing, mocking and how we can mock Drupal's services that are dependencies for our classes.
|
||||
|
||||
In lesson 5, you used Kernel tests to ensure the correct posts were returned from `PostNodeRepository` and in the correct order.
|
||||
|
||||
Let's see how that would look as a unit test.
|
||||
|
||||
## Creating the test
|
||||
|
||||
Create a new test, `PostNodeRepositoryUnitTest` and, for now, just create a new `PostNodeRepository`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// web/modules/custom/atdc/tests/src/Unit/PostNodeRepositoryUnitTest.php
|
||||
|
||||
final class PostNodeRepositoryUnitTest extends UnitTestCase {
|
||||
|
||||
/** @test */
|
||||
public function it_returns_posts(): void {
|
||||
$repository = new PostNodeRepository();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Running the test will give this error:
|
||||
|
||||
> ArgumentCountError: Too few arguments to function Drupal\atdc\Repository\PostNodeRepository::__construct(), 0 passed
|
||||
|
||||
This is expected as `PostNodeRepository` has a dependency - the `EntityTypeManager`.
|
||||
|
||||
But, as this is a unit test, you can't get the Repository from the service container, and you need to instantiate it as well as any dependencies.
|
||||
|
||||
Try to fix this by creating a new `EntityTypeManager` and injecting it into the constructor:
|
||||
|
||||
```php
|
||||
$repository = new PostNodeRepository(
|
||||
new EntityTypeManager(),
|
||||
);
|
||||
```
|
||||
|
||||
Running the tests again will give you a similar error:
|
||||
|
||||
> ArgumentCountError: Too few arguments to function Drupal\Core\Entity\EntityTypeManager::__construct(), 0 passed
|
||||
|
||||
`EntityTypeManager` also has dependencies that need to be injected, and they may have dependencies.
|
||||
|
||||
Instead of doing this manually, let's start using mocks.
|
||||
|
||||
## Adding the first mock
|
||||
|
||||
Add `use Drupal\Core\Entity\EntityTypeManagerInterface;` and create a mock to use instead of the manually created version.
|
||||
|
||||
```php
|
||||
$repository = new PostNodeRepository(
|
||||
$this->createMock(EntityTypeManagerInterface::class),
|
||||
);
|
||||
```
|
||||
|
||||
As the mock implements `EntityTypeManagerInterface`, this will fix the failure, and the test will continue.
|
||||
|
||||
## Getting the posts
|
||||
|
||||
Next, try to get the posts from the Repository:
|
||||
|
||||
```php
|
||||
$repository->findAll();
|
||||
```
|
||||
|
||||
Instead of returning a result, this also results in an error:
|
||||
|
||||
> Error: Call to a member function loadByProperties() on null
|
||||
|
||||
Within `PostNodeRepository`, we use the `getStorage()` on `EntityTypeManager` to get the node storage, which is an instance of `EntityStorageInterface`.
|
||||
|
||||
For the test to work, this needs to be mocked too and returned from the `getStorage()` method.
|
||||
|
||||
Create a mock of `EntityStorageInterface`, which will be used as the node storage:
|
||||
|
||||
```php
|
||||
$nodeStorage = $this->createMock(EntityStorageInterface::class);
|
||||
```
|
||||
|
||||
Next, this needs to be returns from the mock `EntityTypeManager`.
|
||||
|
||||
To do this, specify that the `getStorage()` method when called with the value `node`, will return the mocked node storage:
|
||||
|
||||
```php
|
||||
$entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
|
||||
$entityTypeManager->method('getStorage')->with('node')->willReturn($nodeStorage);
|
||||
|
||||
$repository = new PostNodeRepository($entityTypeManager);
|
||||
```
|
||||
|
||||
This will then be returned instead of `NULL` and fix the error.
|
||||
|
||||
## Creating nodes and adding assertions
|
||||
|
||||
Next, let's create and return the nodes we need and add the assertions.
|
||||
|
||||
You'll need to use a mock for each node and set what each method needs to return.
|
||||
|
||||
The same as the Kernel test, set a title for each post with different created times.
|
||||
|
||||
```php
|
||||
$node1 = $this->createMock(NodeInterface::class);
|
||||
$node1->method('bundle')->willReturn('post');
|
||||
$node1->method('getCreatedTime')->willReturn(strtotime('-1 week'));
|
||||
$node1->method('label')->willReturn('Post one');
|
||||
|
||||
$node2 = $this->createMock(NodeInterface::class);
|
||||
$node2->method('bundle')->willReturn('post');
|
||||
$node2->method('getCreatedTime')->willReturn(strtotime('-8 days'));
|
||||
$node2->method('label')->willReturn('Post two');
|
||||
|
||||
$node3 = $this->createMock(NodeInterface::class);
|
||||
$node3->method('bundle')->willReturn('post');
|
||||
$node3->method('getCreatedTime')->willReturn(strtotime('yesterday'));
|
||||
$node3->method('label')->willReturn('Post three');
|
||||
```
|
||||
|
||||
Then, specify the `loadByProperties` method should return the posts.
|
||||
|
||||
```php
|
||||
$nodeStorage->method('loadByProperties')->willReturn([
|
||||
$node1,
|
||||
$node2,
|
||||
$node3,
|
||||
]);
|
||||
```
|
||||
|
||||
Finally, add some assertions that the nodes returned are the correct ones and in the correct order:
|
||||
|
||||
```php
|
||||
$posts = $repository->findAll();
|
||||
|
||||
self::assertContainsOnlyInstancesOf(NodeInterface::class, $posts);
|
||||
|
||||
$titles = array_map(
|
||||
fn (NodeInterface $node) => $node->label(),
|
||||
$posts,
|
||||
);
|
||||
|
||||
self::assertCount(3, $titles);
|
||||
self::assertSame(
|
||||
['Post two', 'Post one', 'Post three'],
|
||||
$titles,
|
||||
);
|
||||
```
|
||||
|
||||
As the assertions should match the returned values, this test should now pass.
|
||||
|
||||
This is testing the same thing as the kernel test, but it's your preference which way you prefer.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Hopefully, if you run your whole testsuite, you should see output like this:
|
||||
|
||||
```plain
|
||||
PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
|
||||
|
||||
......... 9 / 9 (100%)
|
||||
|
||||
Time: 00:07.676, Memory: 10.00 MB
|
||||
```
|
||||
|
||||
Or, if you use `--testdox`, output like this:
|
||||
|
||||
```plain
|
||||
PHPUnit 9.6.15 by Sebastian Bergmann and contributors.
|
||||
|
||||
Blog Page (Drupal\Tests\atdc\Functional\BlogPage)
|
||||
✔ Blog page
|
||||
✔ Posts are visible
|
||||
✔ Only published nodes are shown
|
||||
✔ Only post nodes are shown
|
||||
|
||||
Post Builder (Drupal\Tests\atdc\Kernel\Builder\PostBuilder)
|
||||
✔ It returns a published post
|
||||
✔ It returns an unpublished post
|
||||
✔ It returns a post with tags
|
||||
|
||||
Post Node Repository (Drupal\Tests\atdc\Kernel\PostNodeRepository)
|
||||
✔ Posts are returned by created date
|
||||
|
||||
Post Node Repository Unit (Drupal\Tests\atdc\Unit\PostNodeRepositoryUnit)
|
||||
✔ It returns posts
|
||||
|
||||
Time: 00:07.097, Memory: 10.00 MB
|
||||
|
||||
OK (9 tests, 71 assertions)
|
||||
```
|
||||
|
||||
Everything should be passing, and your testsuite should have a combination of different types of tests.
|
||||
|
||||
In this course, you've learned:
|
||||
|
||||
- How to configure Drupal and PHPUnit to run automated tests.
|
||||
- How to write functional, kernel and unit tests.
|
||||
- How to create data, such as node types, content and users within tests.
|
||||
- How to manage configuration using test-specific modules.
|
||||
- How to write unit tests and use mocks.
|
||||
- Some small PHP tips and tricks, such as promoted constructor properties and the `@test` and `@testdox` parameters in PHPUnit.
|
||||
|
||||
I couldn't cover everything in a short email course, but I hope it was useful.
|
||||
|
||||
## Questions and feedback
|
||||
|
||||
Thank you for taking my Introduction to Automated Testing in Drupal email course.
|
||||
|
||||
I'd appreciate any feedback, so if you wouldn't mind, press reply and let me know what you thought of the course.
|
||||
|
||||
Also, I'd love to know your next steps are and what I can do to help.
|
||||
|
||||
You can register for my [Daily Email list][daily] to get daily software development emails and updates about future products and courses or see when the next date is for my [online Drupal testing workshop][dto].
|
||||
|
||||
I also offer private workshops and talks for development teams, [1-on-1 consulting calls][call] and [pair programming sessions][pair], [development team coaching][team] and [Drupal development subscriptions][subscription].
|
||||
|
||||
Happy testing!
|
||||
|
||||
Oliver
|
||||
|
||||
[call]: {{site.url}}/call
|
||||
[daily]: {{site.url}}/daily
|
||||
[dto]: {{site.url}}/dto
|
||||
[pair]: {{site.url}}/pair
|
||||
[podcast]: {{site.url}}/podcast
|
||||
[subscription]: {{site.url}}/subscription
|
||||
[team]: {{site.url}}/team-coaching
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue