Magento 2 – Unit Testing Source Models Guide

magento2source-modeltestingunit tests

I have several models in my custom extension that only serve the purpose filling in some selects and/or multiselects in the add/edit form of my entities.
So they are what magento calls "source models".
The values involved are always the same and the methods return the same thing.
How should I unit test those? Or even better, should I write unit tests for them?
Here is an example.
The following class is used for add/edit form for a field called type and for the grid column of the same field.

<?php
namespace Sample\News\Model\Author\Source;

use Magento\Framework\Option\ArrayInterface;

class Type implements ArrayInterface
{
    const COLLABORATOR = 1;
    const EMPLOYEE = 2;

    /**
     * Get options
     *
     * @return array
     */
    public function toOptionArray()
    {
        $_options = [
            [
                'value' => '',
                'label' => ''
            ],
            [
                'value' => self::COLLABORATOR,
                'label' => __('Collaborator')
            ],
            [
                'value' => self::EMPLOYEE,
                'label' => __('Employee')
            ],
        ];
        return $_options;
    }

    /**
     * get options as key value pair
     *
     * @return array
     */
    public function getOptions()
    {
        $_tmpOptions = $this->toOptionArray();
        $_options = [];
        foreach ($_tmpOptions as $option) {
            $_options[$option['value']] = $option['label'];
        }
        return $_options;
    }
}

Best Answer

This is a great thread, and I really like both Answers from @KAndy and @fschmengler.
I would like to add some additional thoughts that I find valuable when asking a question such as "Should I test X?" or "How should I test X?".

What could go wrong?

  • I could make a dumb typo (happens all the time)
    This usually doesn't justify writing a test.
  • Will I copy the code I need from the core or a different module, and then adjust it to my needs?
    I find this actually a very dangerous thing to do that often leaves subtle bugs. In this case, I favor writing a test, if it isn't too expensive. Making source models configuration based actually would make them more risky IMO.
  • Can there be a conflict with a different module?
    This almost only applies to configuration code. In such a case I like to have an integration test that tells me when that happens.
  • Could Magento change the API in a future release?
    Very unlikely in this case, since your code only depends on an interface. But the more concrete classes are involved or if my code extends a core class, this becomes more of a potential risk.
  • A new PHP version could break my code. Or maybe I want to support PHP 5.6 for years to come.
    Again, highly unlikely here, but in some cases I want a test to warn me, should I change the code in future to use incompatible syntax.

How expensive is it to test the code?

This has two aspects:

  • The amount of effort and time it takes to write a test
  • The amount of effort and time it takes to test the piece of code I'm about to write manually.

During development of some piece of code, I tend to have to run the code I'm writing quite often until I consider it done. This is of course much easier with a unit test.

In your case writing a test is dead cheap. It doesn't take much time or effort. @KAndy is right that all code needs to be maintained, but then again, not all tests need to be kept.
This might be an example where I would write a unit test, just to check I don't make a dumb mistake, and then delete it once the class is finished. If a test doesn't provide long term value I think deleting them makes sense.

This question also is important in terms of choosing the type of test to write: unit or integration for example.

How valuable is the piece of code I'm writing?

If a piece of code I'm writing is essential for the service a module provides, I test it, regardless how trivial it is.
If it's just a little utility method, for example UI focused, and not part of the business logic, then maybe not.

Will the code will need to change?

Over time I've become so used to having test coverage, that changing uncovered code feels very insecure. That includes so simple things like adding an option to a source model, but also things like moving a class to a different folder/namespace, or renaming a method.
Having tests in place for such changes is invaluable.

Does it need documentation?

How hard is it to use the code? In you example it's trivial, but in some more complex cases, having a test is great for documentation purposes for other developers (or myself in a few months).

Exploration and Learning

If I'm working on some code and I'm not sure how to test it, I find it very valuable to write a test. The process almost always gives me a deeper understanding of what I'm dealing with.
This is especially true for developers who still consider themselves learning testing.
This also is an example where it might make sense to delete the test afterwards, because the main value it provided was learning.

Discipline and Stress

Sticking to the red-green-refactor loop helps me to go fast. This is especially true under pressure. So even if some piece of code isn't really test-worthy, I might still follow TDD, especially if the code is trivial to test.
This keeps me in the flow and alert.

What and how to test?

Also consider you can write the test in very different granularity.

  • Testing the exact return value.
    This will be a very rigid test that will have to be adjusted to every change. Do you want the test to break, for example, if the order of items in the return array changes?
  • Testing the structure of the return value.
    For the source model this could be checking each sub-array as two records, one with a label and one with a value key.
  • Checking the class implements ArrayInterface.
  • Testing the class provides getOptions() even though that method is not part of the implemented interface.

For each possible thing that can be tested, consider value, maintainability and cost.

Summary

To sum it up: there is no true single answer to a question if some piece of code should be tested or not. The answer will be different for every developer depending on the circumstances.