---
title: "Testing — Imports for Laravel | Roundly"
description: "Imports::fake() records and passes through — every assertion pair, recorded operations, withoutRunning(), spreadsheet builders, factories, workbench."
url: https://roundly-consulting.com/open-source/docs/imports-for-laravel/testing
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Imports for Laravel](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)

# Testing

Imports::fake() swaps the manager for an ImportsFake behind the facade and in the container, and returns it:

```php
use Illuminate\Support\Facades\Bus;
use RoundlyConsulting\Imports\Facades\Imports;
use RoundlyConsulting\Imports\Models\ImportSession;
use RoundlyConsulting\Imports\Testing\SpreadsheetFactory;

it('imports the uploaded assets', function () {
    $fake = Imports::fake();   // records every operation and passes it through
    Bus::fake();               // optional: keep commits from running

    $this->actingAs($admin)->post('/assets/import', ['file' => SpreadsheetFactory::csv(['Asset tag', 'Name'], [['AST-1', 'Laptop']])]);

    Imports::assertUploaded('assets', fn (ImportSession $session) => $session->owner->is($team));
    Imports::assertPreviewed();
    Imports::assertNothingCommitted();
    expect($fake->recorded('upload'))->toHaveCount(1);
});
```

- It records every operation and passes it through: handles keep returning real sessions and batches. Add Bus::fake() to keep commits from running.
- It records calls made through the facade, an injected manager (ImportsFake extends ImportsManager), every handle and the model conveniences ($session->handle(), $batch->handle()).
- Reads — status(), counts(), rows(), suggestMapping(), … — are not recorded.
- \->withoutRunning() records without running the operations that return nothing a test needs — cancel, discard, resume, rollback, mapping deletes, prune and sweep (they return null or an empty report). mappings(…)->delete() returns true, as for an existing mapping, so the delete-mapping endpoint answers 200; the recorded result stays null. Everything else still runs.

```php
use RoundlyConsulting\Imports\Models\ImportBatch;

$fake = Imports::fake()->withoutRunning();   // cancel, discard, resume, rollback, deleteMapping, prune, sweep: record only

Imports::for($team)->by($admin)->batch($batchId)->rollback();   // recorded, not run

Imports::assertRolledBack(fn (ImportBatch $batch) => $batch->getKey() === $batchId);
$fake->recorded('rollback')[0]->action;     // the action class it would have run
```

## Assertions

Every assertion has a negative pair and is listed on the facade, so Imports::assertModeChosen() completes in an IDE after Imports::fake(). A callback receives the model and returns bool. assertMappingSaved() covers session saves and mappings()->save() alike:

| Assertion | Negative | Callback receives |
| --- | --- | --- |
| `assertUploaded(?string $importer = null, ?Closure $callback = null)` | `assertNothingUploaded()` | ImportSession |
| `assertFilesUploaded(?string $importer = null, ?Closure $callback = null)` | `assertNoFilesUploaded()` | ImportSession |
| `assertSourceConfigured(?Closure $callback = null)` | `assertNoSourceConfigured()` | ImportSession |
| `assertMapped(?Closure $callback = null)` | `assertNothingMapped()` | ImportSession |
| `assertMappingSaved(?string $name = null)` | `assertNoMappingSaved()` | — |
| `assertMappingDeleted(?string $name = null)` | `assertNoMappingDeleted()` | — |
| `assertModeChosen(?DuplicateMode $mode = null, ?Closure $callback = null)` | `assertNoModeChosen()` | ImportSession |
| `assertPreviewed(?Closure $callback = null)` | `assertNothingPreviewed()` | ImportSession |
| `assertCommitted(?string $importer = null, ?Closure $callback = null)` | `assertNothingCommitted()` | ImportBatch |
| `assertCancelled(?Closure $callback = null)` | `assertNothingCancelled()` | ImportBatch |
| `assertResumed(?Closure $callback = null)` | `assertNothingResumed()` | ImportBatch |
| `assertRolledBack(?Closure $callback = null)` | `assertNothingRolledBack()` | ImportBatch |
| `assertDiscarded(?Closure $callback = null)` | `assertNothingDiscarded()` | ImportSession |
| `assertTemplateBuilt(?string $importer = null, ?TemplateFormat $format = null, ?string $locale = null)` | `assertNoTemplateBuilt()` | — |
| `assertErrorReportBuilt(?Closure $callback = null)` | `assertNoErrorReportBuilt()` | ImportSession |
| `assertPruned()` | `assertNotPruned()` | — |
| `assertSwept()` | `assertNotSwept()` | — |
| `assertNothingHappened()` | — | — |

## Recorded operations

recorded(?string $operation = null) returns the RecordedOperation list — each with operation, context (the session, batch, importer, owner, mode, format, … it was called with), result and action (the action class that ran it). Operation names: upload, uploadFiles, configureSource, map, saveMapping, deleteMapping, mode, preview, template, errorReport, commit, cancel, resume, rollback, discard, prune, sweep.

## Building files

SpreadsheetFactory builds CSV, XLSX and ZIP uploads — each returns an UploadedFile. An empty header list writes no header row, XLSX rows also take DateTimeInterface values, and encoding also takes UTF-16LE, UTF-16BE, Windows-1252 and Windows-1250 to test the rejections:

```php
use RoundlyConsulting\Imports\Testing\SpreadsheetFactory;

SpreadsheetFactory::csv(['Asset tag', 'Name'], [['AST-1', 'Laptop']], delimiter: ';', bom: true, encoding: 'UTF-8', eol: "\n", name: 'import.csv');
SpreadsheetFactory::xlsx(['Asset tag', 'Name'], [['AST-1', 'Laptop']], sheet: 'Assets', extraSheets: ['Other' => [['A']]], date1904: false, sharedStrings: true, name: 'import.xlsx');
SpreadsheetFactory::zip(['a.pdf' => '%PDF', 'b.pdf' => '%PDF'], name: 'files.zip');
SpreadsheetFactory::file('notes.txt', 'hello', mime: 'text/plain');

SpreadsheetFactory::csv(['Name'], [['Žltý kôň']], encoding: 'Windows-1250');   // to test the not_utf8 rejection
```

## Factories

ImportSession, ImportBatch, ImportRowResult and ImportedRecord all ship factories:

```php
use RoundlyConsulting\Imports\Enums\SessionStatus;
use RoundlyConsulting\Imports\Models\{ImportedRecord, ImportSession};

ImportSession::factory()->owner($team)->status(SessionStatus::Previewed)->create();
ImportedRecord::factory()->inBatch($batch)->record($asset)->create();
```

## Queued previews and commits

```php
// Run queued previews and commits in a test
Artisan::call('queue:work', ['--stop-when-empty' => true]);

// or run everything inline
config(['imports.queue.enabled' => false]);
```

## Try it locally

The package’s repository ships a workbench app for manual review: two teams, each with an admin who may import and a member who may not, vendors to reference, three importers (assets, people, documents) and the JSON API on:

```bash
vendor/bin/testbench workbench:build      # a fresh SQLite database, every migration, the seeder
vendor/bin/testbench serve                # http://127.0.0.1:8000
vendor/bin/testbench queue:work           # in a second terminal: large previews, commits and undo
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)
