diff --git a/CHANGELOG.md b/CHANGELOG.md index 831f8e1..eea388b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Unit testing tips to the README file — + [73](https://github.com/dartoos-dev/json_cache/issues/73). + ## [1.2.1] - 2022-04-02 ### Changed diff --git a/README.md b/README.md index dc699be..e326535 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ Rultor.com](https://www.rultor.com/b/dartoos-dev/json_cache)](https://www.rultor - [JsonCacheLocalStorage — LocalStorage](#jsoncachelocalstorage) - [JsonCacheHive — Hive](#jsoncachehive) - [JsonCacheCrossLocalStorage — CrossLocalStorage](#jsoncachecrosslocalstorage) +- [Unit Test Tips](#unit-test-tips) + - [Suggested Dependency Relationship](#suggested-dependency-relationship) + - [Using Fake Implementation](#using-fake-implementation) + - [Widget Testing](#widget-testing) + - [Example of Widget Test Code](#example-of-widget-test-code) - [Demo application](#demo-application) - [Contribute](#contribute) - [References](#references) @@ -256,7 +261,80 @@ is an implementation on top of the … final LocalStorageInterface prefs = await LocalStorage.getInstance(); final JsonCache jsonCache = JsonCacheMem(JsonCacheCrossLocalStorage(prefs)); - … +``` + +## Unit Test Tips + +This package has been designed with unit testing in mind. This is one of the +reasons for the existence of the `JsonCache` interface. + +### Suggested Dependency Relationship + +Whenever a function, method, or class needs to interact with user data, it +should do so via a reference to the `JsonCache` interface rather than relying on +an actual implementation. + +See the code snippet below: + +```dart +/// Stores/retrieves user data from the device's local storage. +class JsonCacheRepository implements ILocalRepository { + /// Sets the [JsonCache] instance. + const JsonCacheRepository(this._cache); + // This class depends on an interface rather than any actual implementation + final JsonCache _cache; + + /// Retrieves a cached email by [userId] or `null` if not found. + @override + Future getUserEmail(String userId) async { + final userData = await _cache.value(userId); + if (userData != null) { + // the email value or null if absent. + return userData['email'] as String?; + } + // There is no data associated with [userId]. + return null; + } +} +``` + +By depending on an interface rather than an actual implementation, the code +above is [loosely coupled](https://en.wikipedia.org/wiki/Loose_coupling) to this +package — which means it's easy to test as you can +[mock](https://docs.flutter.dev/cookbook/testing/unit/mocking) the `JsonCache` +dependency. + +### Using Fake Implementation + +In addition to mocking, there is another approach to unit testing: making use of +a 'fake' implementation. Usually this so called 'fake' implementation provides +the functionality required by the `JsonCache` interface without touching the +device's local storage. An example of this implementation is the +[JsonCacheFake](https://pub.dev/documentation/json_cache/latest/json_cache/JsonCacheFake-class.html) +class — whose sole purpose is to help developers with unit tests. + +### Widget Testing + +Because of the asynchronous nature of dealing with cached data, you're better +off putting all your test code inside a `tester.runAsync` method; otherwise, +your test case may stall due to a +[deadlock](https://en.wikipedia.org/wiki/Deadlock) caused by a [race +condition](https://stackoverflow.com/questions/34510/what-is-a-race-condition) +as there might be multiple `Futures` trying to access the same resources. + +#### Example of Widget Test Code + +Your widget test code should look similar to the following code snippet: + +```dart +testWidgets('refresh cached value', (WidgetTester tester) async { + final LocalStorage localStorage = LocalStorage('my_cached_data'); + final jsonCache = JsonCacheMem(JsonCacheLocalStorage(localStorage)); + tester.runAsync(() async { + // asynchronous code inside runAsync. + await jsonCache.refresh('test', {'aKey': 'aValue'}); + }); +}); ``` ## Demo application @@ -293,5 +371,4 @@ Make sure the command below **passes** before making a Pull Request. ## References -- [Caching for objects](https://www.pragmaticobjects.com/chapters/012_caching_for_objects.html) - [Dart and race conditions](https://pub.dev/packages/mutex) diff --git a/analysis_options.yaml b/analysis_options.yaml index 511bc97..36c6284 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -13,8 +13,6 @@ linter: sort_constructors_first: true # Good packages document everything public_member_api_docs: true - # Always await. - unawaited_futures: true always_declare_return_types: true cancel_subscriptions: true close_sinks: true diff --git a/pubspec.lock b/pubspec.lock index e04ad8c..621732d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "31.0.0" + version: "41.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.8.0" + version: "4.2.0" args: dependency: transitive description: @@ -57,13 +57,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" clock: dependency: transitive description: @@ -77,7 +70,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -126,7 +119,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: @@ -246,7 +239,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" lint: dependency: "direct dev" description: @@ -281,7 +274,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -323,7 +316,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_provider: dependency: transitive description: @@ -510,7 +503,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -545,21 +538,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.19.5" + version: "1.21.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.13" typed_data: dependency: transitive description: @@ -573,7 +566,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" vm_service: dependency: transitive description: @@ -624,5 +617,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.16.0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=2.5.0"