From 4137f6cffa5641bbef29c13999fd471f025f632d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 29 Sep 2019 16:21:09 +0200 Subject: [PATCH] Write unit tests for DelegateDatabase --- .../src/runtime/executor/helpers/engines.dart | 1 + .../test/engines/delegated_database_test.dart | 179 ++++++++++++++++++ moor/test/insert_test.dart | 18 +- moor/test/join_test.dart | 16 ++ moor/test/schema_test.dart | 2 +- 5 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 moor/test/engines/delegated_database_test.dart diff --git a/moor/lib/src/runtime/executor/helpers/engines.dart b/moor/lib/src/runtime/executor/helpers/engines.dart index d89df199..04368236 100644 --- a/moor/lib/src/runtime/executor/helpers/engines.dart +++ b/moor/lib/src/runtime/executor/helpers/engines.dart @@ -168,6 +168,7 @@ class _TransactionExecutor extends TransactionExecutor Future send() async { if (_sendOnCommit != null) { await runCustom(_sendOnCommit, const []); + _db.delegate.isInTransaction = false; } _sendCalled.complete(); diff --git a/moor/test/engines/delegated_database_test.dart b/moor/test/engines/delegated_database_test.dart new file mode 100644 index 00000000..9c3ecc74 --- /dev/null +++ b/moor/test/engines/delegated_database_test.dart @@ -0,0 +1,179 @@ +import 'package:mockito/mockito.dart'; +import 'package:moor/backends.dart'; +import 'package:moor/moor.dart'; +import 'package:test/test.dart'; + +class _MockDelegate extends Mock implements DatabaseDelegate {} + +class _MockUserDb extends Mock implements GeneratedDatabase {} + +class _MockDynamicVersionDelegate extends Mock + implements DynamicVersionDelegate {} + +class _MockTransactionDelegate extends Mock + implements SupportedTransactionDelegate {} + +void main() { + _MockDelegate delegate; + setUp(() { + delegate = _MockDelegate(); + + when(delegate.isOpen).thenAnswer((_) => Future.value(true)); + when(delegate.runSelect(any, any)) + .thenAnswer((_) => Future.value(QueryResult.fromRows([]))); + when(delegate.runUpdate(any, any)).thenAnswer((_) => Future.value(3)); + when(delegate.runInsert(any, any)).thenAnswer((_) => Future.value(4)); + when(delegate.runCustom(any, any)).thenAnswer((_) => Future.value()); + when(delegate.runBatched(any)).thenAnswer((_) => Future.value()); + }); + + group('delegates queries', () { + void _runTests(bool sequential) { + test('when sequential = $sequential', () async { + final db = DelegatedDatabase(delegate, isSequential: sequential); + + await db.doWhenOpened((_) async { + expect(await db.runSelect(null, null), isEmpty); + expect(await db.runUpdate(null, null), 3); + expect(await db.runInsert(null, null), 4); + await db.runCustom(null); + await db.runBatched(null); + }); + + verifyInOrder([ + delegate.isOpen, + delegate.runSelect(null, null), + delegate.runUpdate(null, null), + delegate.runInsert(null, null), + delegate.runCustom(null, []), + delegate.runBatched(null), + ]); + }); + } + + _runTests(false); + _runTests(true); + }); + + group('migrations', () { + DelegatedDatabase db; + _MockUserDb userDb; + setUp(() { + userDb = _MockUserDb(); + when(userDb.schemaVersion).thenReturn(3); + + when(delegate.isOpen).thenAnswer((_) => Future.value(false)); + db = DelegatedDatabase(delegate)..databaseInfo = userDb; + + when(userDb.handleDatabaseCreation(executor: anyNamed('executor'))) + .thenAnswer((i) async { + final executor = i.namedArguments.values.single as SqlExecutor; + await executor('created', []); + }); + + when(userDb.handleDatabaseVersionChange( + executor: anyNamed('executor'), + from: anyNamed('from'), + to: anyNamed('to'), + )).thenAnswer((i) async { + final executor = i.namedArguments[#executor] as SqlExecutor; + final from = i.namedArguments[#from] as int; + final to = i.namedArguments[#to] as int; + await executor('upgraded', [from, to]); + }); + }); + + test('when the database does not support versions', () async { + when(delegate.versionDelegate).thenReturn(const NoVersionDelegate()); + await db.doWhenOpened((_) async {}); + + verify(delegate.open(userDb)); + verifyNever(delegate.runCustom(any, any)); + }); + + test('when the database supports versions at opening', () async { + when(delegate.versionDelegate) + .thenReturn(OnOpenVersionDelegate(() => Future.value(3))); + await db.doWhenOpened((_) async {}); + + verify(delegate.open(userDb)); + verifyNever(delegate.runCustom(any, any)); + }); + + test('when the database supports dynamic version', () async { + final version = _MockDynamicVersionDelegate(); + when(version.schemaVersion).thenAnswer((_) => Future.value(3)); + + when(delegate.versionDelegate).thenReturn(version); + await db.doWhenOpened((_) async {}); + + verify(delegate.open(userDb)); + verifyNever(delegate.runCustom(any, any)); + verify(version.schemaVersion); + verify(version.setSchemaVersion(3)); + }); + + test('handles database creations', () async { + when(delegate.versionDelegate) + .thenReturn(OnOpenVersionDelegate(() => Future.value(0))); + await db.doWhenOpened((_) async {}); + + verify(delegate.runCustom('created', [])); + }); + + test('handles database upgrades', () async { + when(delegate.versionDelegate) + .thenReturn(OnOpenVersionDelegate(() => Future.value(1))); + await db.doWhenOpened((_) async {}); + + verify(delegate.runCustom('upgraded', [1, 3])); + }); + }); + + group('transactions', () { + DelegatedDatabase db; + + setUp(() { + db = DelegatedDatabase(delegate, isSequential: true); + }); + + test('when the delegate does not support transactions', () async { + when(delegate.transactionDelegate) + .thenReturn(const NoTransactionDelegate()); + await db.doWhenOpened((_) async { + final transaction = db.beginTransaction(); + await transaction.doWhenOpened((e) async { + await e.runSelect(null, null); + + await transaction.send(); + }); + }); + + verifyInOrder([ + delegate.runCustom('BEGIN TRANSACTION', []), + delegate.runSelect(null, null), + delegate.runCustom('COMMIT TRANSACTION', []), + ]); + }); + + test('when the database supports transactions', () async { + final transaction = _MockTransactionDelegate(); + when(transaction.startTransaction(any)).thenAnswer((i) { + (i.positionalArguments.single as Function(QueryDelegate))(delegate); + }); + + when(delegate.transactionDelegate).thenReturn(transaction); + + await db.doWhenOpened((_) async { + final transaction = db.beginTransaction(); + await transaction.doWhenOpened((e) async { + await e.runSelect(null, null); + + await transaction.send(); + }); + }); + + verify(transaction.startTransaction(any)); + }); + }); +} diff --git a/moor/test/insert_test.dart b/moor/test/insert_test.dart index cd708a1f..d3b54b1d 100644 --- a/moor/test/insert_test.dart +++ b/moor/test/insert_test.dart @@ -18,6 +18,7 @@ void main() { test('generates insert statements', () async { await db.into(db.todosTable).insert(const TodosTableCompanion( content: Value('Implement insert statements'), + title: Value.absent(), )); verify(executor.runInsert('INSERT INTO todos (content) VALUES (?)', @@ -111,16 +112,21 @@ void main() { verify(streamQueries.handleTableUpdates({db.users})); }); - test('enforces data integrity', () { - expect( - db.into(db.todosTable).insert( + test('enforces data integrity', () async { + InvalidDataException exception; + try { + await db.into(db.todosTable).insert( const TodosTableCompanion( // not declared as nullable in table definition content: Value(null), ), - ), - throwsA(const TypeMatcher()), - ); + ); + fail('inserting invalid data did not throw'); + } on InvalidDataException catch (e) { + exception = e; + } + + expect(exception.toString(), startsWith('InvalidDataException')); }); test('reports auto-increment id', () async { diff --git a/moor/test/join_test.dart b/moor/test/join_test.dart index 731b6953..b6faa1e0 100644 --- a/moor/test/join_test.dart +++ b/moor/test/join_test.dart @@ -1,4 +1,5 @@ import 'package:moor/moor.dart'; +import 'package:moor/src/runtime/components/join.dart'; import 'package:test/test.dart'; import 'data/tables/todos.dart'; import 'data/utils/mocks.dart'; @@ -113,4 +114,19 @@ void main() { verify(executor.runSelect( argThat(contains('WHERE t.id < ? ORDER BY t.title ASC')), [3])); }); + + test('injects custom error message when a table is used multiple times', + () async { + when(executor.runSelect(any, any)).thenAnswer((_) => Future.error('nah')); + + MoorWrappedException wrappedException; + try { + await db.select(db.todosTable).join([crossJoin(db.todosTable)]).get(); + fail('expected this to throw'); + } on MoorWrappedException catch (e) { + wrappedException = e; + } + + expect(wrappedException.toString(), contains('possible cause')); + }); } diff --git a/moor/test/schema_test.dart b/moor/test/schema_test.dart index 7f513544..e43fe7fe 100644 --- a/moor/test/schema_test.dart +++ b/moor/test/schema_test.dart @@ -17,7 +17,7 @@ void main() { group('Migrations', () { test('creates all tables', () async { - await Migrator(db, mockQueryExecutor).createAllTables(); + await db.handleDatabaseCreation(executor: mockQueryExecutor); // should create todos, categories, users and shared_todos table verify(mockQueryExecutor.call(