The $data
can be used to populate data on your object since the constructor of \Magento\Framework\DataObject
is this
public function __construct(array $data = [])
{
$this->_data = $data;
}
or similar for Magento\Framework\Api\AbstractSimpleObject
/**
* Initialize internal storage
*
* @param array $data
*/
public function __construct(array $data = [])
{
$this->_data = $data;
}
which a lot of classes in Magento extend from.
A common use is in conjunction with a factory. For example in
Magento\Sales\Model\Order\CustomerManagement
we have:
$this->addressFactory->create(['data' => $addressData]);
which essentially populates the $data
array at creation.
Having to keep the $data = []
at the end of the list of parameters is normal php behaviour since you are assigning a default value - the empty array.
TL;DR
You are using the getCollectionMock()
method correctly.
The problem is that it only mocks the getIterator()
method.
Your code however calls toOptionArray()
on the collection, not getIterator()
.
Because the collection is a mock, by default all its methods only return null
, (including toOptionArray()
).
That is why your test fails with a null
as the assertion value.
Long version
One possibility would be to create the collection mock excluding that toOptionArray()
method from being mocked. This would be a "partial mock" and they are considered bad style, mainly because of other methods that are called internally that then also would need to be excluded and so on.
Another option would be to explicitly specify the behavior of the toOptionArray()
method on the mock:
$collection->method('toOptionArray')->willReturn(['1' => 'Romania']);
In this case this probably also isn't what you want, as you are simply testing the mocks behavior, which is pointless.
So what to do?
In this case I think the most sensible thing to do would be to just check the source model calls toOptionArray()
on the collection and returns the results, ignoring the format of the result (since that is core code you don't want to test).
For example:
protected function setUp()
{
$objectManager = new ObjectManager($this);
$countryCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
$this->collectionMock = $objectManager->getCollectionMock(Collection::class, []);
$countryCollectionFactoryMock->method('create')->willReturn($this->collectionMock);
$this->countryList = $objectManager->getObject(Country::class, [
'countryCollectionFactory' => $countryCollectionFactoryMock,
]);
}
public function testDelegatesToCollectionToCreateTheOptionsArray()
{
$this->collectionMock->method('toOptionArray')->willReturn(['foo' => 'bar']);
$this->collectionMock->expects($this->atLeastOnce())->method('toOptionArray');
$this->assertSame(['foo' => 'bar'], $this->countryList->toOptionArray());
}
This test would fail if either the collections toOptionArray()
method is not called or if the method doesn't return the same array that the mock returned.
Another example for a test that might make sense during TDD is to check the result collection method call is memoized:
public function testMemoizedOptionArray()
{
$this->collectionMock->method('toOptionArray')->willReturn(['baz' => 'qux']);
$this->collectionMock->expects($this->once())->method('toOptionArray');
$result1 = $this->countryList->toOptionArray();
$result2 = $this->countryList->toOptionArray();
$this->assertSame($result1, $result2);
}
For this test to succeed the toOptionArray()
method will have to be called exactly one time, and both method calls have to be exactly the same.
But what about getCollectionMock()
???
To make real use of the mocked collection you have to write code that uses the collection iterator, for example by using it with foreach
, or by calling getIterator()
directly.
public function toOptionArray()
{
if (is_null($this->options)) {
foreach ($this->countryCollectionFactory->create() as $country) {
$this->options[$country->getId()] = $country->getTitle();
}
}
return $this->options;
}
}
Best Answer
every class that extends the
DataObject
class contains a member called_data
.From here you can get the values using
$this->getData('key_here')
.That will return
_data['key_here']
.The
hasData
method checks if that key is set.For example
$this->hasData('test')
is an equivalent forarray_key_exists('test', $this->_data)
.Here is where the method is defined.
If your model is a flat table model (all values are kept in a single table) the key should be one of the column names.
For example when having a cms page model,
$this->hasData('identifier')
will check if the page identifier is set.If the model is an EAV model, the key should be one of the entity attributes.
For example for products,
$this->hasData('name')
will check if the product name is set.But, in general you can set anything to the
_data
member on any model using$this->setData('some_key', 'some_value')
.In this case, the key can be any string.
Then you can check if the data for a certain key is set with
$this->hasData('some_key');