Testing an import 

Import behaviour is tested with functional tests: a real database, a real DataHandler, and upstream responses served from fixture files instead of the network.

The test case 

Import tests extend \WerkraumMedia\ThueCat\Tests\Functional\AbstractImportTestCase . Read it before writing the first test; most of what a test needs is already there.

Upstream responses
Every request a run makes has to be announced. expectFetch() serves a JSON-LD file for one URL, expectNotFound() answers 404, expectFailure() answers any other status. A request nobody announced fails the test, and so does an announced request that never happened. A test therefore also documents exactly what a run fetches.
Fixture files
Response bodies live under Tests/Functional/Fixtures/Import/Guzzle/, in a path mirroring the URL: host, path, then the resource id as file name.
Database state
Pages, existing records and configurations are seeded from PHP data sets in Tests/Functional/Fixtures/Import/; expected states after an import live in Tests/Functional/Assertions/Import/.
Reading results
countRows() , fetchUidByRemoteId() and fetchRowByRemoteId() query the database ignoring deleted rows, but no other restriction — hidden records and every language count. Use them instead of building queries in the test.

werkraummedia/events must be among the extensions a functional test loads. TYPO3 13 enforces the dependency; 14 tolerates its absence, so a test can pass on one version and fail on the other.

Habits that keep tests honest 

Count the default language
A table holds translations too. A count over the whole table passes for the wrong reasons; look records up by remote_id instead.
Seed instead of importing twice
A re-import test seeds the rows a previous run would have left and imports once. Running the import twice in one test mostly proves that a run does what a run does.
Test removal along with addition
A new relation needs a test that removes it, and one where a fetch fails next to a surviving entry, see Sets of shared targets.
Use placeholders for structural roles
Where a fixture needs a value that must not match anything real — an unmapped type, a foreign host — use an obviously invented one, not a real upstream URI.

The PHPUnit configuration ships with the package as phpunit.xml.dist.