Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
BundleOptionsTest.php
Go to the documentation of this file.
1 <?php
6 declare(strict_types=1);
7 
9 
11 use PHPUnit_Framework_MockObject_MockObject as MockObject;
13 use Magento\Framework\Pricing\Adjustment\Calculator as AdjustmentCalculator;
14 use Magento\Framework\Pricing\PriceInfo\Base as BasePriceInfo;
21 use Magento\Bundle\Pricing\Adjustment\Calculator as BundleAdjustmentCalculator;
23 use Magento\Bundle\Model\Product\Type as BundleProductType;
24 use Magento\Bundle\Model\ResourceModel\Option\Collection as BundleOptionCollection;
26 use Magento\Tax\Helper\Data as TaxHelperData;
27 
32 class BundleOptionsTest extends \PHPUnit\Framework\TestCase
33 {
37  private $bundleOptions;
38 
42  private $baseCalculator;
43 
47  private $objectManagerHelper;
48 
52  private $saleableItemMock;
53 
57  private $bundleCalculatorMock;
58 
62  private $selectionFactoryMock;
63 
67  private $amountFactory;
68 
72  private $priceInfoMock;
73 
74  protected function setUp()
75  {
76  $this->priceInfoMock = $this->getMockBuilder(BasePriceInfo::class)
77  ->disableOriginalConstructor()
78  ->getMock();
79  $this->saleableItemMock = $this->getMockBuilder(Product::class)
80  ->disableOriginalConstructor()
81  ->getMock();
82  $priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)->getMock();
83  $priceCurrency->expects($this->any())->method('round')->willReturnArgument(0);
84 
85  $this->selectionFactoryMock = $this->getMockBuilder(BundleSelectionFactory::class)
86  ->disableOriginalConstructor()
87  ->getMock();
88  $this->amountFactory = $this->getMockBuilder(AmountFactory::class)
89  ->disableOriginalConstructor()
90  ->getMock();
91  $factoryCallback = $this->returnCallback(
92  function ($fullAmount, $adjustments) {
93  return $this->createAmountMock(['amount' => $fullAmount, 'adjustmentAmounts' => $adjustments]);
94  }
95  );
96  $this->amountFactory->expects($this->any())->method('create')->will($factoryCallback);
97  $this->baseCalculator = $this->getMockBuilder(AdjustmentCalculator::class)
98  ->disableOriginalConstructor()
99  ->getMock();
100 
101  $taxData = $this->getMockBuilder(TaxHelperData::class)
102  ->disableOriginalConstructor()
103  ->getMock();
104 
105  $this->bundleCalculatorMock = $this->getMockBuilder(BundleAdjustmentCalculator::class)
106  ->setConstructorArgs(
107  [$this->baseCalculator, $this->amountFactory, $this->selectionFactoryMock, $taxData, $priceCurrency]
108  )
109  ->setMethods(['getOptionsAmount'])
110  ->getMock();
111  $this->objectManagerHelper = new ObjectManagerHelper($this);
112  $this->bundleOptions = $this->objectManagerHelper->getObject(
113  BundleOptions::class,
114  [
115  'calculator' => $this->bundleCalculatorMock,
116  'bundleSelectionFactory' => $this->selectionFactoryMock,
117  ]
118  );
119  }
120 
127  public function testGetOptions(array $selectionCollection)
128  {
129  $this->prepareOptionMocks($selectionCollection);
130  $this->bundleOptions->getOptions($this->saleableItemMock);
131  $this->assertSame($selectionCollection, $this->bundleOptions->getOptions($this->saleableItemMock));
132  }
133 
139  private function prepareOptionMocks(array $selectionCollection)
140  {
141  $this->saleableItemMock->expects($this->atLeastOnce())
142  ->method('getStoreId')
143  ->willReturn(1);
144  $priceTypeMock = $this->getMockBuilder(BundleProductType::class)
145  ->disableOriginalConstructor()
146  ->getMock();
147  $priceTypeMock->expects($this->atLeastOnce())
148  ->method('setStoreFilter')
149  ->with(1, $this->saleableItemMock)
150  ->willReturnSelf();
151  $optionIds = ['41', '55'];
152  $priceTypeMock->expects($this->atLeastOnce())
153  ->method('getOptionsIds')
154  ->with($this->saleableItemMock)
155  ->willReturn($optionIds);
156  $priceTypeMock->expects($this->atLeastOnce())
157  ->method('getSelectionsCollection')
158  ->with($optionIds, $this->saleableItemMock)
159  ->willReturn($selectionCollection);
160  $collection = $this->getMockBuilder(BundleOptionCollection::class)
161  ->disableOriginalConstructor()
162  ->getMock();
163  $collection->expects($this->atLeastOnce())
164  ->method('appendSelections')
165  ->with($selectionCollection, true, false)
166  ->willReturn($selectionCollection);
167  $priceTypeMock->expects($this->atLeastOnce())
168  ->method('getOptionsCollection')
169  ->with($this->saleableItemMock)
170  ->willReturn($collection);
171  $this->saleableItemMock->expects($this->atLeastOnce())
172  ->method('getTypeInstance')
173  ->willReturn($priceTypeMock);
174  }
175 
179  public function getOptionsDataProvider() : array
180  {
181  return [
182  [
183  ['1', '2'],
184  ],
185  ];
186  }
187 
197  public function testGetOptionSelectionAmount(float $selectionQty, $selectionAmount, bool $useRegularPrice)
198  {
199  $selection = $this->createPartialMock(Product::class, ['getSelectionQty', '__wakeup']);
200  $amountInterfaceMock = $this->getMockBuilder(AmountInterface::class)
201  ->getMockForAbstractClass();
202  $amountInterfaceMock->expects($this->once())
203  ->method('getValue')
204  ->willReturn($selectionAmount);
205  $selection->expects($this->once())
206  ->method('getSelectionQty')
207  ->willReturn($selectionQty);
208  $priceMock = $this->getMockBuilder(BundleSelectionPrice::class)
209  ->disableOriginalConstructor()
210  ->getMock();
211  $priceMock->expects($this->once())
212  ->method('getAmount')
213  ->willReturn($amountInterfaceMock);
214  $this->selectionFactoryMock->expects($this->once())
215  ->method('create')
216  ->with($this->saleableItemMock, $selection, $selectionQty)
217  ->willReturn($priceMock);
218  $optionSelectionAmount = $this->bundleOptions->getOptionSelectionAmount(
219  $this->saleableItemMock,
220  $selection,
221  $useRegularPrice
222  );
223  $this->assertSame($selectionAmount, $optionSelectionAmount->getValue());
224  }
225 
229  public function selectionAmountDataProvider(): array
230  {
231  return [
232  [1., 50.5, false],
233  [2.2, false, true],
234  ];
235  }
236 
243  private function createAmountMock(array $amountData)
244  {
246  $amount = $this->getMockBuilder(BaseAmount::class)
247  ->disableOriginalConstructor()
248  ->getMock();
249  $amount->expects($this->any())->method('getAdjustmentAmounts')
250  ->willReturn($amountData['adjustmentAmounts'] ?? []);
251  $amount->expects($this->any())->method('getValue')->willReturn($amountData['amount']);
252 
253  return $amount;
254  }
255 
262  private function createOptionMock(array $optionData)
263  {
265  $option = $this->createPartialMock(BundleOption::class, ['isMultiSelection', '__wakeup']);
266  $option->expects($this->any())->method('isMultiSelection')
267  ->willReturn($optionData['isMultiSelection']);
268  $selections = [];
269  foreach ($optionData['selections'] as $selectionData) {
270  $selections[] = $this->createSelectionMock($selectionData);
271  }
272  foreach ($optionData['data'] as $key => $value) {
273  $option->setData($key, $value);
274  }
275  $option->setData('selections', $selections);
276 
277  return $option;
278  }
279 
286  private function createSelectionMock(array $selectionData)
287  {
288  $selection = $this->getMockBuilder(Product::class)
289  ->setMethods(['isSalable', 'getAmount', 'getQuantity', 'getProduct', '__wakeup'])
290  ->disableOriginalConstructor()
291  ->getMock();
292 
293  // All items are saleable
294  $selection->expects($this->any())->method('isSalable')->willReturn(true);
295  foreach ($selectionData['data'] as $key => $value) {
296  $selection->setData($key, $value);
297  }
298  $amountMock = $this->createAmountMock($selectionData['amount']);
299  $selection->expects($this->any())->method('getAmount')->willReturn($amountMock);
300  $selection->expects($this->any())->method('getQuantity')->willReturn(1);
301 
302  $innerProduct = $this->getMockBuilder(Product::class)
303  ->setMethods(['getSelectionCanChangeQty', '__wakeup'])
304  ->disableOriginalConstructor()
305  ->getMock();
306  $innerProduct->expects($this->any())->method('getSelectionCanChangeQty')->willReturn(true);
307  $selection->expects($this->any())->method('getProduct')->willReturn($innerProduct);
308 
309  return $selection;
310  }
311 
319  public function testCalculation(array $optionList, array $expected)
320  {
321  $storeId = 1;
322  $this->saleableItemMock->expects($this->any())->method('getStoreId')->willReturn($storeId);
323  $this->selectionFactoryMock->expects($this->any())->method('create')->willReturnArgument(1);
324 
325  $this->baseCalculator->expects($this->atLeastOnce())->method('getAmount')
326  ->willReturn($this->createAmountMock(['amount' => 0.]));
327 
328  $options = [];
329  foreach ($optionList as $optionData) {
330  $options[] = $this->createOptionMock($optionData);
331  }
333  $optionsCollection = $this->getMockBuilder(BundleOptionCollection::class)
334  ->disableOriginalConstructor()
335  ->getMock();
336  $optionsCollection->expects($this->atLeastOnce())->method('appendSelections')->willReturn($options);
337 
339  $typeMock = $this->getMockBuilder(BundleProductType::class)
340  ->disableOriginalConstructor()
341  ->getMock();
342  $typeMock->expects($this->any())->method('setStoreFilter')
343  ->with($storeId, $this->saleableItemMock);
344  $typeMock->expects($this->any())->method('getOptionsCollection')
345  ->with($this->saleableItemMock)
346  ->willReturn($optionsCollection);
347  $this->saleableItemMock->expects($this->any())->method('getTypeInstance')->willReturn($typeMock);
348 
349  $this->assertEquals($expected['min'], $this->bundleOptions->calculateOptions($this->saleableItemMock));
350  $this->assertEquals($expected['max'], $this->bundleOptions->calculateOptions($this->saleableItemMock, false));
351  }
352 
356  public function getTestDataForCalculation(): array
357  {
358  return [
359  'first case' => [
360  'optionList' => [
361  // first option with single choice of product
362  [
363  'isMultiSelection' => false,
364  'data' => [
365  'title' => 'test option 1',
366  'default_title' => 'test option 1',
367  'type' => 'select',
368  'option_id' => '1',
369  'position' => '0',
370  'required' => '1',
371  ],
372  'selections' => [
373  [
374  'data' => ['price' => 70.],
375  'amount' => ['amount' => 70],
376  ],
377  [
378  'data' => ['price' => 80.],
379  'amount' => ['amount' => 80],
380  ],
381  [
382  'data' => ['price' => 50.],
383  'amount' => ['amount' => 50],
384  ],
385  ],
386  ],
387  // second not required option
388  [
389  'isMultiSelection' => false,
390  'data' => [
391  'title' => 'test option 2',
392  'default_title' => 'test option 2',
393  'type' => 'select',
394  'option_id' => '2',
395  'position' => '1',
396  'required' => '0',
397  ],
398  'selections' => [
399  [
400  'data' => ['value' => 20.],
401  'amount' => ['amount' => 20],
402  ],
403  ],
404  ],
405  // third with multi-selection
406  [
407  'isMultiSelection' => true,
408  'data' => [
409  'title' => 'test option 3',
410  'default_title' => 'test option 3',
411  'type' => 'select',
412  'option_id' => '3',
413  'position' => '2',
414  'required' => '1',
415  ],
416  'selections' => [
417  [
418  'data' => ['price' => 40.],
419  'amount' => ['amount' => 40],
420  ],
421  [
422  'data' => ['price' => 20.],
423  'amount' => ['amount' => 20],
424  ],
425  [
426  'data' => ['price' => 60.],
427  'amount' => ['amount' => 60],
428  ],
429  ],
430  ],
431  // fourth without selections
432  [
433  'isMultiSelection' => true,
434  'data' => [
435  'title' => 'test option 3',
436  'default_title' => 'test option 3',
437  'type' => 'select',
438  'option_id' => '4',
439  'position' => '3',
440  'required' => '1',
441  ],
442  'selections' => [],
443  ],
444  ],
445  'expected' => ['min' => 70, 'max' => 220],
446  ],
447  ];
448  }
449 }
$optionData
$amount
Definition: order.php:14
$value
Definition: gender.phtml:16
$selectionCollection
testGetOptionSelectionAmount(float $selectionQty, $selectionAmount, bool $useRegularPrice)