Skip to content

Commit 2d15c7d

Browse files
authored
fix: add informative messages to error classes (#144)
1 parent 833fcba commit 2d15c7d

4 files changed

Lines changed: 115 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Release notes are available on [github][notes].
1414
- Error messages now include the variable name for easier debugging
1515
- [fix] `load(isOptional: true)` no longer discards successfully loaded base file when an override file is missing or empty (fixes #70, #93, #101, #125)
1616
- [fix] `clean()` now resets `isInitialized` to `false`, so accessing `env` after `clean()` correctly throws `NotInitializedError`
17+
- [fix] Error classes (`NotInitializedError`, `FileNotFoundError`, `EmptyEnvFileError`) now include informative messages in `toString()` instead of the unhelpful `Instance of 'ClassName'` (fixes #72, #127; improves diagnostics for #59, #89)
18+
- `FileNotFoundError` and `EmptyEnvFileError` now carry the filename when available
19+
20+
### Note on error message improvements
21+
`NotInitializedError`, `FileNotFoundError`, and `EmptyEnvFileError` now override `toString()` with actionable messages (e.g., `FileNotFoundError: Environment file ".env" not found. Ensure the file exists and is listed under assets in pubspec.yaml.`). This is **not a breaking change** — the class names and hierarchy are unchanged, so existing `on FileNotFoundError` catch clauses continue to work. `FileNotFoundError` now accepts an optional positional `filename` parameter, and `EmptyEnvFileError` accepts an optional named `filename` parameter; both default to `null` for backward compatibility.
1722

1823
### Note on release-build behavior change
1924
In **debug mode**, behavior is unchanged — `AssertionError` was thrown before, `AssertionError` is thrown now.

lib/src/dotenv.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ class DotEnv {
216216
WidgetsFlutterBinding.ensureInitialized();
217217
var envString = await rootBundle.loadString(filename);
218218
if (envString.isEmpty) {
219-
throw EmptyEnvFileError();
219+
throw EmptyEnvFileError(filename: filename);
220220
}
221221
return envString.split('\n');
222222
} on FlutterError {
223-
throw FileNotFoundError();
223+
throw FileNotFoundError(filename);
224224
}
225225
}
226226

lib/src/errors.dart

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
1-
class NotInitializedError extends Error {}
1+
class NotInitializedError extends Error {
2+
@override
3+
String toString() =>
4+
'NotInitializedError: DotEnv has not been initialized. '
5+
'Call load() or loadFromString() before accessing env variables.';
6+
}
27

3-
class FileNotFoundError extends Error {}
8+
class FileNotFoundError extends Error {
9+
final String? filename;
10+
FileNotFoundError([this.filename]);
411

5-
class EmptyEnvFileError extends Error {}
12+
@override
13+
String toString() => filename != null
14+
? 'FileNotFoundError: Environment file "$filename" not found. '
15+
'Ensure the file exists and is listed under assets in pubspec.yaml.'
16+
: 'FileNotFoundError: Environment file not found. '
17+
'Ensure the file exists and is listed under assets in pubspec.yaml.';
18+
}
19+
20+
class EmptyEnvFileError extends Error {
21+
final String? filename;
22+
EmptyEnvFileError({this.filename});
23+
24+
@override
25+
String toString() => filename != null
26+
? 'EmptyEnvFileError: Environment file "$filename" is empty.'
27+
: 'EmptyEnvFileError: The provided env string is empty.';
28+
}

test/errors_test.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import 'package:flutter_dotenv/flutter_dotenv.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
void main() {
5+
group('NotInitializedError', () {
6+
test('toString contains actionable guidance', () {
7+
final error = NotInitializedError();
8+
expect(error.toString(), contains('NotInitializedError'));
9+
expect(error.toString(), contains('load()'));
10+
expect(error.toString(), contains('loadFromString()'));
11+
});
12+
13+
test('is thrown when accessing env before initialization', () {
14+
final env = DotEnv();
15+
expect(() => env.env, throwsA(isA<NotInitializedError>()));
16+
});
17+
18+
test('thrown error message does not reference global singleton', () {
19+
final env = DotEnv();
20+
try {
21+
env.env;
22+
fail('should have thrown');
23+
} on NotInitializedError catch (e) {
24+
expect(e.toString(), contains('load()'));
25+
expect(e.toString(), isNot(contains('dotenv.load')));
26+
}
27+
});
28+
});
29+
30+
group('FileNotFoundError', () {
31+
test('toString contains the filename when provided', () {
32+
final error = FileNotFoundError('.env');
33+
expect(error.toString(), contains('FileNotFoundError'));
34+
expect(error.toString(), contains('.env'));
35+
expect(error.toString(), contains('pubspec.yaml'));
36+
});
37+
38+
test('toString provides generic message when no filename given', () {
39+
final error = FileNotFoundError();
40+
expect(error.toString(), contains('FileNotFoundError'));
41+
expect(error.toString(), contains('not found'));
42+
});
43+
44+
test('filename field is accessible', () {
45+
expect(FileNotFoundError('.env.production').filename, '.env.production');
46+
expect(FileNotFoundError().filename, isNull);
47+
});
48+
});
49+
50+
group('EmptyEnvFileError from loadFromString', () {
51+
test('thrown error message describes empty string context', () {
52+
final env = DotEnv();
53+
try {
54+
env.loadFromString(envString: '');
55+
fail('should have thrown');
56+
} on EmptyEnvFileError catch (e) {
57+
expect(e.toString(), contains('empty'));
58+
expect(e.filename, isNull);
59+
}
60+
});
61+
});
62+
63+
group('EmptyEnvFileError', () {
64+
test('toString contains the filename when provided', () {
65+
final error = EmptyEnvFileError(filename: '.env');
66+
expect(error.toString(), contains('EmptyEnvFileError'));
67+
expect(error.toString(), contains('.env'));
68+
expect(error.toString(), contains('empty'));
69+
});
70+
71+
test('toString provides generic message when no filename given', () {
72+
final error = EmptyEnvFileError();
73+
expect(error.toString(), contains('EmptyEnvFileError'));
74+
expect(error.toString(), contains('empty'));
75+
});
76+
77+
test('filename field is accessible', () {
78+
expect(EmptyEnvFileError(filename: '.env').filename, '.env');
79+
expect(EmptyEnvFileError().filename, isNull);
80+
});
81+
});
82+
}

0 commit comments

Comments
 (0)