Testing
Flutter apps using drift can always be tested with integration tests running on a real device. This guide focuses on writing unit tests for a database written in drift. Those tests can be run and debugged on your computer without additional setup, you don't need a physical device to run them.
Setup¶
For this guide, we're going to test a very simple database that stores user names. The only important change from a regular drift
database is the constructor: We make the QueryExecutor
argument explicit instead of having a no-args constructor that passes
a fixed executor (like a FlutterQueryExecutor
or a NativeDatabase
) to the superclass:
import 'package:drift/drift.dart';
part 'database.g.dart';
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
@DriftDatabase(tables: [Users])
class MyDatabase extends _$MyDatabase {
MyDatabase(QueryExecutor e) : super(e);
@override
int get schemaVersion => 1;
/// Creates a user and returns their id
Future<int> createUser(String name) {
return into(users).insert(UsersCompanion.insert(name: name));
}
/// Changes the name of a user with the [id] to the [newName].
Future<void> updateName(int id, String newName) {
return update(users).replace(User(id: id, name: newName));
}
Stream<User> watchUserWithId(int id) {
return (select(users)..where((u) => u.id.equals(id))).watchSingle();
}
}
Installing sqlite
We can't distribute an sqlite installation as a pub package (at least not as something that works outside of a Flutter build), so you need to ensure that you have the sqlite3 shared library installed on your system.
On macOS, it's installed by default.
On Linux, you can use the libsqlite3-dev
package on Ubuntu and the
sqlite3
package on Arch (other distros will have similar packages).
On Windows, you can download 'Precompiled Binaries for Windows'
and extract sqlite3.dll
into a folder that's in your PATH
environment variable. Then restart your device to ensure that
all apps will run with this PATH
change.
As sqlite3_flutter_libs
bundles the latest sqlite3 version with your app,
using a recent sqlite3 version is recommended to avoid differences in how tests
behave from your app.
The minimum sqlite version tested with drift is 3.29.0, but many drift features
like returning
or generated columns will require more recent versions.
Writing tests¶
We can create an in-memory version of the database by using a
NativeDatabase.memory()
instead of a FlutterQueryExecutor
or other implementations. A good
place to open the database is the setUp
and tearDown
methods from
package:test
:
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:test/test.dart';
// the file defined above, you can test any drift database of course
import 'database.dart';
void main() {
MyDatabase database;
setUp(() {
database = MyDatabase(DatabaseConnection(
NativeDatabase.memory(),
closeStreamsSynchronously: true, // (1)!
));
});
tearDown(() async {
await database.close();
});
}
- By default, unsubscribing from a query stream created by drift will keep the stream open for one event
loop iteration. This is useful for e.g. Flutter apps, where rebuilds may cause a
StreamBuilder
to re-subscribe to streams frequently. In Flutter widget tests however, it's illegal to keep these timers open after a test concludes. To avoid issues with Drift in that setups, pass aDatabaseConnection
withcloseStreamsSynchronously: true
to your database.
With that setup in place, we can finally write some tests:
test('users can be created', () async {
final id = await database.createUser('some user');
final user = await database.watchUserWithId(id).first;
expect(user.name, 'some user');
});
test('stream emits a new user when the name updates', () async {
final id = await database.createUser('first name');
final expectation = expectLater(
database.watchUserWithId(id).map((user) => user.name),
emitsInOrder(['first name', 'changed name']),
);
await database.updateName(id, 'changed name');
await expectation;
});
Testing migrations¶
Drift can help you generate code for schema migrations. For more details, see this guide.