Search code examples
phpsymfonytestingtddphpspec

Testing attaching UploadedFile to form submit with PhpSpec


I am attempting to test an image importer which takes a file and submits a form with that image attached. That part of the test seems to be the part where it is falling over and i'm not sure how to resolve it.

I understand the concept of not "mocking what you don't own", however without acknowledging the call to the private method insertImageThroughForm the test does not work.

I get the following error:

75  ! imports image assets
        method call:
          - submit(["file" => ["assetFile" => ["file" => Symfony\Component\HttpFoundation\File\UploadedFile:0000000027d380bc00007f8c6cad27ec Object (
            'test' => false
            'originalName' => 'picco.jpg'
            'mimeType' => 'application/octet-stream'
            'size' => null
            'error' => 0
        )]]], false)
        on Double\Symfony\Component\Form\Form\P22 was not expected, expected calls were:
          - submit(exact(["file" => ["assetFile" => ["file" => Double\Symfony\Component\Finder\SplFileInfo\P19:0000000027d3807500007f8c6cad27ec Object (
            'objectProphecy' => Prophecy\Prophecy\ObjectProphecy Object (*Prophecy*)
            'relativePath' => null
            'relativePathname' => null
        )]]]), exact(false))
          - submit(exact(["file" => ["assetFile" => ["file" => Double\Symfony\Component\Finder\SplFileInfo\P20:0000000027d3803000007f8c6cad27ec Object (
            'objectProphecy' => Prophecy\Prophecy\ObjectProphecy Object (*Prophecy*)
            'relativePath' => null
            'relativePathname' => null
        )]]]), exact(false))

ImageImporterSpec.php

const TEMP_PATH = '/tmp/crmpicco/imageImporter';

function it_imports_image_assets(
    Category $category,
    AssetFactory $assetFactory,
    Asset $asset,
    Finder $finder,
    SplFileInfo $file1,
    SplFileInfo $file2,
    FormFactory $formFactory,
    Form $form
) {
  $category->getId()->willReturn(14);

  $createTempPathFilesystem = new Filesystem();
  $createTempPathFilesystem->mkdir(self::TEMP_PATH);
  $createTempPathFilesystem->mkdir(self::TEMP_PATH . DIRECTORY_SEPARATOR . 'courses');

  $imageImportPath = self::TEMP_PATH . DIRECTORY_SEPARATOR . 'courses/';

  $createTempPathFilesystem->touch($imageImportPath . 'picco.jpg');
  $createTempPathFilesystem->touch($imageImportPath . 'morton.jpg');

  $finder->files()->willReturn($finder);
  $finder->in($imageImportPath)->willReturn($finder);

  $finder->getIterator()->willReturn(new \ArrayIterator([
      $file1->getWrappedObject(),
      $file2->getWrappedObject(),
  ]));

  $file1->getPathname()->willReturn($imageImportPath . 'picco.jpg');
  $file2->getPathname()->willReturn($imageImportPath . 'morton.jpg');

  $assetFactory->createForCategory(14)->willReturn($asset);

  $formFactory->create('category_asset', $asset, ['csrf_protection' => false])->willReturn($form);

  $form->submit(['file' => ['assetFile' => ['file' => $file1->getWrappedObject()]]], false)->shouldBeCalled();
  $form->submit(['file' => ['assetFile' => ['file' => $file2->getWrappedObject()]]], false)->shouldBeCalled();

  $this->importImageAssets('courses/', $category)->shouldBeCalled();
}

ImageImporter.php

public function importImageAssets($importDirectory, Category $category)
{
    $finder = new Finder();
    $finder->files()->in($importDirectory);
    if (count($finder) > 0) {
        foreach ($finder as $image) {
            $filename = $image->getBasename('.' . $image->getExtension());
            $filepath = $importDirectory . '/' . $image->getFilename();

            $asset = $this->assetFactory->createForCategory($category->getId());

            $asset->setName($filename);

            $this->insertImageThroughForm($asset, $filepath);

            $this->entityManager->persist($asset);
        }
        $this->entityManager->flush();
    }
}

 private function insertImageThroughForm(Asset $asset, $filePath)
 {
  $form = $this->formFactory->create('category_asset', $asset, ['csrf_protection' => false]);

  $uploadedFile = new UploadedFile($filePath, basename($filePath));

  $form->submit(['file' => ['assetFile' => ['file' => $uploadedFile]]], false);
}

Solution

  • What you're testing is more an integration test than a unit test. PhpSpec is not suitable for integration tests, but only for low unit tests. Why it's an integration test? Because you use a real filesystem which is external service and also few 3rd party libraries like Filesystem and Finder.

    You're trying to mock the Finder but the Finder is directly initialized within the method so can't be mocked. You need to inject the Finder as a dependency of the class.