28 CSV import test cases, including the ones that corrupt data silently
Twenty-eight test cases for CSV import and bulk operations — encoding, date ambiguity, leading zeros, formula injection, partial failure and the re-import that duplicates everything.
- 28 cases
- 7 coverage types
- Last verified Sep 16, 2026
A CSV import either fails loudly or corrupts your data quietly. The loud failures are easy — a wrong file type, a missing column, a file that is not a CSV at all. Every team tests those.
The quiet ones are the reason this set exists. A phone number that loses its leading zero. A date that reads 03/04 as March the fourth in one country and April the third in another. A re-import that creates a second copy of every row instead of updating the first. None of these produce an error. They produce a database that is wrong in a way nobody notices for a month.
Twenty-eight cases below, weighted towards the quiet half.
Showing 28 of 28 cases
| ID | Test case | Type | Priority | Preconditions | Steps | Test data | Expected result |
|---|---|---|---|---|---|---|---|
| CSV-01 | A valid file imports every row | Functional | High | An import-capable account |
| 100 valid rows | 100 records created, and the reported count matches what is in the database. |
| CSV-02 | Columns can be mapped to fields | Functional | High | The file's headers differ from the field names |
| headers in a different order | Values land in the mapped fields. Column order in the file does not matter. |
| CSV-03 | A preview is shown before anything is written | Functional | High | — |
| 10 rows | The first rows and the detected mapping are shown, and nothing is written until the import is confirmed. |
| CSV-04 | Cancelling at the preview writes nothing | Functional | High | A file at the preview stage |
| — | No records are created and no partial state is left behind. |
| CSV-05 | Re-importing the same file updates rather than duplicates | Data integrity | High | The file was already imported once |
| the same 100 rows | Still 100 records. Matching is on the documented key, not on row position. |
| CSV-06 | A partial failure reports every bad row | Functional | High | A file with some invalid rows |
| 95 valid, 5 invalid | The outcome is stated per the documented contract, and all five failures are named with their row numbers and reasons. |
| CSV-07 | The failed rows can be downloaded and re-imported | Functional | Medium | An import with failures |
| the error file | The file has the original columns plus a reason, and the corrected version imports cleanly. |
| CSV-08 | Progress is visible on a long import | Usability | Medium | A large file |
| 50000 rows | Progress is reported and the page is usable. The request does not simply hang until it is done. |
| CSV-09 | A non-CSV file is refused | Negative | High | — |
| a renamed binary | Refused on content, not on extension alone. |
| CSV-10 | An empty file is refused | Negative | High | — |
| — | Refused with a clear message. Not an unhandled error. |
| CSV-11 | A header-only file is refused or reports zero | Negative | Medium | — |
| headers only | Handled per the documented contract, with a message. Never a success claiming rows were imported. |
| CSV-12 | A missing required column is refused | Negative | High | — |
| no email column | Refused before any row is written, naming the missing column. |
| CSV-13 | A duplicate header is refused | Negative | Medium | — |
| two email columns | Refused with an explanation, rather than silently keeping one of them. |
| CSV-14 | Broken quoting is reported with a row number | Negative | High | — |
| an unterminated quoted field | The error names the row, and no partial import runs. |
| CSV-15 | A row with too many columns is reported | Negative | Medium | — |
| one ragged row | That row is reported by number. The extra value is never shifted into the next field. |
| CSV-16 | A file over the size limit is refused | Boundary | Medium | The documented limit is known |
| limit plus 1MB | Refused before the whole file is read, with the limit stated. |
| CSV-17 | A row count over the limit is refused | Boundary | Medium | The documented row limit is known |
| limit plus 1 row | Refused with the limit stated and a suggestion to split the file. |
| CSV-18 | A cell at the maximum field length imports whole | Boundary | Medium | — |
| 255 characters | Stored whole, not truncated without warning. |
| CSV-19 | A one-row file imports | Boundary | Low | — |
| 1 row | One record created. |
| CSV-20 | Leading zeros survive | Data integrity | High | — |
| 00123 and 0044 7700 900123 | The values are stored as written. They are not read as numbers and shortened. |
| CSV-21 | Long numbers are not turned into scientific notation | Data integrity | High | — |
| 123456789012345678 | Stored exactly. Not rounded and not rendered as an exponent. |
| CSV-22 | Dates are read in one documented order | Data integrity | High | — |
| 03/04/2026 and 04/03/2026 | Both are read per the stated format, and an unparseable date is an error rather than a guess. |
| CSV-23 | UTF-8 with a byte order mark imports cleanly | Data integrity | High | A file exported from a spreadsheet on Windows |
| a BOM and accented characters | The first header is not prefixed with a stray character, and the accents survive. |
| CSV-24 | Both line endings are accepted | Data integrity | Medium | — |
| the same file twice | Identical results. |
| CSV-25 | A formula in a cell is neutralised on export | Security | High | The data can be exported again |
| =1+1 and =cmd |'/c calc'!A0 | The cell is escaped on export so the spreadsheet shows text rather than evaluating it. |
| CSV-26 | A row cannot be imported into another account | Security | High | Two accounts |
| another account's record id | Refused or ignored. The import respects the same ownership rules as the interface. |
| CSV-27 | An import that fails partway leaves no half-written state | Data integrity | High | A file that fails at the last row |
| 99 good rows and a fatal one | The result matches the documented contract — all or nothing, or a named partial set — and there are no orphaned child records either way. |
| CSV-28 | The upload control is keyboard operable and errors are announced | Accessibility | High | Screen reader running |
| — | The control is reachable and labelled, and the error is announced rather than only shown. |
Case IDs are positional within this set, not stable identifiers. Import the set, then let your own tool assign its IDs.
How to use this set
Build one deliberately nasty fixture file and keep it in the repository. Most of the quiet cases here — the leading zeros, the long number, the ambiguous date, the BOM, the formula — can live as five columns in a single file, so one import exercises all of them.
The re-import case (CSV-05) decides the shape of the whole feature. Import-only means every correction is a manual edit afterwards, which is why people re-upload the file and end up with two of everything.
What we deliberately left out
Spreadsheet formats with multiple sheets, formulas and merged cells are a different parsing problem and get their own set. Scheduled and API-driven imports share most of these cases but add retry and partial-batch behaviour worth covering separately.
Questions about this set
Our import runs in a background job. Does this still apply?
Yes, and add the job cases — what a user sees while it runs, what happens if the worker restarts halfway, and whether the same file can be queued twice.
Why treat leading zeros as a high priority?
Because postcodes, phone numbers and account references all have them, the failure is invisible at import time, and it is usually discovered when someone cannot be contacted.
Is all-or-nothing better than a partial import?
Neither is wrong. What is wrong is not documenting which one you do, because the user's next action depends entirely on it.
Somewhere to keep these once you have run them
Tesbo holds the cases, the runs and the results in one place, so the next release starts from what the last one proved.