In [Wednesday's email]({{site.url}}/daily/2022/09/14/simpletest-drupal-test), I showed how quick it is to get started writing automated tests for a new Drupal module, starting with a functional test.
I prefer the outside-in style (or London approach) of test-driven development, where I start with a the highest-level test that I can for a task. If the task needs me to make a HTTP request, then I’ll use a functional test. If not, I’ll use a kernel (or integration) test.
I find that these higher-level types of tests are easier and quicker to set up compared to starting with lower-level unit tests, cover more functionality, and make it easier to refactor.
## An example
For example, this `Device` class which is a data transfer object around Drupal's `NodeInterface`. It ensures that the correct type of node is provided, and includes a named constructor and a helper method to retrieve a device's asset ID from a field:
public static function fromNode(NodeInterface $node): self {
return new self($node);
}
}
```
## Testing getting the asset ID using a unit test
As the `Node::create()` method (what I'd normally use to create a node) interacts with the database, I need to create a mock node to wrap with my DTO.
I need to specify what value is returned from the `bundle()` method as well as getting the asset ID field value.
I need to mock the `get()` method and specify the field name that I'm getting the value for, which also returns it's own mock for `FieldItemListInterface` with a value set for the `getString()` method.
I can create a real `Node` object, pass that to the `Device` DTO, and call the `getAssetId()` method.
As I can interact with the database, there's no need to create mocks or define return values.
The 'arrange' step is much smaller, and I think that this is easier to read and understand.
### Trade-offs
Even though the test is cleaner, because there are no mocks there's other setup to do, including having the required configuration available, enabling modules, and installing schemas and configuration as part of the test - and having test-specific modules to store the needed configuration files.
Because of this, functional and kernel tests will take more time to run than unit tests, but an outside-in approach could be worth considering, depending on your project and team.