testing library
Fakes that stand in for the platform, so a tap can be exercised in a test. Test code only.
This library exists for a hardware reason rather than a convenience one. NFCTagReaderSession
does not start in the Simulator and no emulator has an NFC radio, so replacing the platform
side is the only mechanism by which any CI runs a tag through an app at all. Everything here
is inert -- no channel is touched, no session is started, and no handle addresses a tag -- so
an app that reaches for it from its own lib/ ships that inertness to its users.
debugReplaceApis puts a fake platform in place and hands back the function that puts the
real one back; fakeNfcTag builds the tag the platform would have delivered; and the three
Fake*HostApi classes answer every call, so a test writes only the ones it cares about.
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:nfc_util/android.dart';
import 'package:nfc_util/testing.dart';
class _Card extends FakeNfcAndroidHostApi {
@override
Future<Uint8List> mifareClassicReadBlock(String handle, int blockIndex) async =>
Uint8List.fromList(List.filled(16, blockIndex));
}
void main() {
late void Function() restore;
setUp(() => restore = debugReplaceApis(android: _Card()));
tearDown(() => restore());
test('reads the block it asked for', () async {
final tag = fakeNfcTag(techs: [FakeTech.nfcA(), FakeTech.mifareClassic()]);
expect(await MifareClassic.from(tag)!.readBlock(blockIndex: 4), everyElement(4));
});
}
One thing this library deliberately does not hand over: the generated wire classes. Every
call that answers a plain Dart value -- the transceives, the Mifare reads, the capability
questions -- can be overridden with nothing but the import above, but a call that names a
generated class or enum anywhere in its signature can only be overridden by a test that
imports package:nfc_util/src/pigeon.g.dart itself. That covers more than the obvious
return types: ndefRead and the ISO 7816 exchanges answer one, and resetTech and the
card-emulation queries answer nothing at all but still take one as a parameter. That is an implementation
import the analyzer flags, and a shape that changes without a major version, which is why it
is not re-exported from here. Say it with a public type where the two are interchangeable --
a message handed to FakeTech.ndefAndroid and read back as Ndef.from(tag)?.cachedMessage,
rather than an overridden ndefRead -- and where they are not, the import is the price.
Classes
- FakeNfcAndroidHostApi
- The Android half of the platform side, answering without a channel.
- FakeNfcHostApi
- The cross-platform half of the platform side, answering without a channel.
- FakeNfcIosHostApi
- The iOS half of the platform side, answering without a channel.
- FakeTech
- One technology on a tag built by fakeNfcTag, as the platform would report it.
Functions
-
debugReplaceApis(
{NfcHostApi? nfc, NfcAndroidHostApi? android, NfcIosHostApi? ios}) → void Function() - Replaces the host APIs with fakes, and returns a function that puts the real ones back.
-
fakeNfcTag(
{String handle = 'fake-tag', Uint8List? id, List< FakeTech> techs = const [], List<String> ? techList, int? otherTagCount}) → NfcTag - A tag that answers to the technologies given, and to nothing else.