Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 79 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<String?> 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', <String, dynamic>{'aKey': 'aValue'});
});
});
```

## Demo application
Expand Down Expand Up @@ -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)
2 changes: 0 additions & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 13 additions & 20 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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"