Skip to content

Commit f03d70a

Browse files
committed
refactor, fix bug, add exemple
1 parent a791bfe commit f03d70a

File tree

6 files changed

+278
-63
lines changed

6 files changed

+278
-63
lines changed

exemples/lib/main.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:firebase_database/firebase_database.dart';
2+
import 'package:firebase_database_mocks/firebase_database_mocks.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
class UserRepository {
6+
UserRepository(this.firebaseDatabase);
7+
FirebaseDatabase firebaseDatabase;
8+
9+
Future<String> getUserName(String userId) async {
10+
final userNode =
11+
firebaseDatabase.reference().child('users').child('$userId/name');
12+
final dataSnapshot = await userNode.once();
13+
return dataSnapshot.value;
14+
}
15+
}
16+
17+
void main() {
18+
FirebaseDatabase firebaseDatabase;
19+
UserRepository userRepository;
20+
// Put fake data
21+
const userId = 'userId';
22+
const userName = 'Elon musk';
23+
MockFirebaseDatabase.instance
24+
.reference()
25+
.child('users')
26+
.child(userId)
27+
.child('name')
28+
.set(userName);
29+
setUp(() {
30+
firebaseDatabase = MockFirebaseDatabase.instance;
31+
userRepository = UserRepository(firebaseDatabase);
32+
});
33+
test('Should contain value', () async {
34+
final userNameFromFakeDatabase = userRepository.getUserName(userId);
35+
expect(userNameFromFakeDatabase, equals(userName));
36+
});
37+
}

lib/firebase_database_mocks.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Todo document all files.
2+
// Todo add license file.
3+
/// A mock for flutter flutter
4+
library firebase_database_mocks;
5+
6+
export 'src/mock_firebase_database.dart';
7+
export 'src/set_up_mocks.dart';

lib/src/mock_database_reference.dart

Lines changed: 94 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ import 'mock_firebase_database.dart';
88
class MockDatabaseReference extends Mock implements DatabaseReference {
99
var _nodePath = '/';
1010
// ignore: prefer_final_fields
11-
static final _persitedData = <String, dynamic>{};
11+
static var _persitedData = <String, dynamic>{};
1212
var _volatileData = <String, dynamic>{};
1313
MockDatabaseReference();
1414
MockDatabaseReference._(nodePath, [this._volatileData]) {
1515
_nodePath += nodePath;
1616
}
17+
// TODO implement real [onchange] (may yield each change).
18+
Stream<Event> get onValue async* {
19+
final data = await once();
20+
yield MockEvent._(data.value);
21+
}
1722

1823
Map<String, dynamic> get _data {
1924
if (MockFirebaseDatabase.persistData) {
@@ -22,6 +27,13 @@ class MockDatabaseReference extends Mock implements DatabaseReference {
2227
return _volatileData;
2328
}
2429

30+
set _data(data) {
31+
if (MockFirebaseDatabase.persistData) {
32+
_persitedData = data;
33+
} else
34+
return _volatileData = data;
35+
}
36+
2537
@override
2638
String get path => _nodePath;
2739

@@ -38,78 +50,98 @@ class MockDatabaseReference extends Mock implements DatabaseReference {
3850
@override
3951
// ignore: missing_return
4052
Future<void> set(dynamic value, {dynamic priority}) {
53+
if (_nodePath == '/') {
54+
_data = value;
55+
return null;
56+
}
4157
var nodePathWithoutSlashesAtEndAndStart =
4258
_nodePath.substring(1, _nodePath.length - 1);
4359
var nodesList = nodePathWithoutSlashesAtEndAndStart.split('/');
4460
var tempData = <String, dynamic>{};
4561
Map<String, dynamic> lastNodeInCurrentData;
4662
var nodeIndexReference = _Int(0);
47-
if (_data.isEmpty) {
63+
if (_data[nodesList.first] == null) {
4864
lastNodeInCurrentData = _data;
4965
} else {
50-
lastNodeInCurrentData = getNode(
51-
data: _data,
52-
nodesList: nodesList,
53-
nodeIndex: nodeIndexReference.increment(),
54-
);
66+
lastNodeInCurrentData = _getNextNodeData(
67+
data: _data, nodesList: nodesList, nodeIndex: nodeIndexReference);
5568
}
5669
var nodeIndex = nodeIndexReference.value;
57-
if (nodesList.length <= nodeIndex) {
70+
var noNewNodeToAdd = nodesList.length <= nodeIndex;
71+
if (noNewNodeToAdd) {
5872
lastNodeInCurrentData[nodesList.last] = value;
5973
return null;
6074
}
6175
var firstNodeInNewData = nodesList[nodeIndex++];
6276
if (nodeIndex < nodesList.length) {
63-
for (; nodeIndex < nodesList.length; nodeIndex++) {
64-
if (nodeIndex + 1 < nodesList.length) {
65-
tempData[nodesList[nodeIndex]] = {nodesList[nodeIndex + 1]: null};
66-
tempData = tempData[nodesList[nodeIndex]];
67-
} else {
68-
tempData[nodesList[nodeIndex]] = value;
69-
}
70-
}
71-
lastNodeInCurrentData
72-
.addAll({firstNodeInNewData: _makeSupportGenericValue(tempData)});
77+
tempData = _buildNewNodesTree(
78+
nodeIndex: nodeIndex,
79+
nodesList: nodesList,
80+
data: tempData,
81+
value: value,
82+
);
83+
lastNodeInCurrentData.addAll({firstNodeInNewData: tempData});
7384
} else {
74-
if (value is Map) value = _makeSupportGenericValue(value);
85+
if (value is Map) value = value;
7586
lastNodeInCurrentData.addAll({firstNodeInNewData: value});
7687
}
7788
}
7889

79-
dynamic getNode(
80-
{@required dynamic data,
81-
@required List<String> nodesList,
82-
@required _Int nodeIndex}) {
83-
// print(data);
84-
// print(nodeIndex.value);
85-
if (nodesList.length == nodeIndex.value ||
86-
!(data[nodesList[nodeIndex.value]] is Map)) return data;
87-
return getNode(
88-
data: data[nodesList[nodeIndex.value]],
89-
nodesList: nodesList,
90-
nodeIndex: nodeIndex.increment());
90+
Map<String, dynamic> _buildNewNodesTree({
91+
@required dynamic data,
92+
@required List<String> nodesList,
93+
@required int nodeIndex,
94+
@required value,
95+
}) {
96+
var nextNodeIndex = nodeIndex + 1;
97+
if (nodeIndex + 1 < nodesList.length) {
98+
data[nodesList[nodeIndex]] = {nodesList[nextNodeIndex]: Object()};
99+
_buildNewNodesTree(
100+
data: data[nodesList[nodeIndex]],
101+
nodesList: nodesList,
102+
nodeIndex: nextNodeIndex,
103+
value: value);
104+
} else
105+
data[nodesList[nodeIndex]] = value;
106+
return data;
107+
}
108+
109+
_getNextNodeData({
110+
@required dynamic data,
111+
@required List<String> nodesList,
112+
@required _Int nodeIndex,
113+
}) {
114+
if (nodesList.length <= nodeIndex.value ||
115+
!(data[nodesList[nodeIndex.value]] is Map)) {
116+
nodeIndex.increment();
117+
return data;
118+
}
119+
return _getNextNodeData(
120+
data: data[nodesList[nodeIndex.value]],
121+
nodesList: nodesList,
122+
nodeIndex: nodeIndex.increment(),
123+
);
91124
}
92125

93126
@override
94127
Future<DataSnapshot> once() {
95-
return Future(() {
96-
var tempData = _data;
97-
var nodePath = _nodePath.substring(1, _nodePath.length - 1);
98-
var nodeList = nodePath.split('/');
99-
if (nodeList.length > 1) {
100-
for (var node in nodeList) {
101-
nodePath = node;
102-
if (tempData[node] == null) {
103-
nodePath = '';
104-
break;
105-
}
106-
if (tempData[node] is Map) {
107-
tempData = tempData[node];
108-
}
128+
var tempData = _data;
129+
// remove start and end slashes.
130+
var nodePath = _nodePath.substring(1, _nodePath.length - 1);
131+
var nodeList = nodePath.split('/');
132+
if (nodeList.length > 1) {
133+
for (var i = 0; i < nodeList.length; i++) {
134+
nodePath = nodeList[i];
135+
var nonExistentNodeFound = tempData[nodePath] == null;
136+
if (nonExistentNodeFound || (i + 1) == nodeList.length) {
137+
break;
138+
}
139+
if (tempData[nodePath] is Map) {
140+
tempData = tempData[nodePath];
109141
}
110142
}
111-
return MockDataSnapshot(tempData[nodePath]);
112-
});
143+
}
144+
return Future.value(MockDataSnapshot(tempData[nodePath]));
113145
}
114146
}
115147

@@ -122,13 +154,19 @@ class _Int {
122154
}
123155
}
124156

125-
Map<String, Object> _makeSupportGenericValue(Map<String, dynamic> data) {
126-
var _dataWithGenericValue = {'__generic_mock_data_value__': Object()};
127-
_dataWithGenericValue.addAll(data);
128-
_dataWithGenericValue.forEach((key, value) {
129-
if (value is Map) {
130-
_dataWithGenericValue[key] = _makeSupportGenericValue(value);
131-
}
132-
});
133-
return _dataWithGenericValue;
157+
class MockEvent extends Mock implements Event {
158+
MockEvent._(data) : snapshot = MockDataSnapshot(data);
159+
160+
final DataSnapshot snapshot;
134161
}
162+
163+
// Map<String, dynamic> _makeSupportGenericValue(Map<String, dynamic> data) {
164+
// var _dataWithGenericValue = {'__generic_mock_data_value__': Object()};
165+
// _dataWithGenericValue.addAll(data);
166+
// _dataWithGenericValue.forEach((key, value) {
167+
// if (value is Map) {
168+
// _dataWithGenericValue[key] = _makeSupportGenericValue(value);
169+
// }
170+
// });
171+
// return _dataWithGenericValue;
172+
// }

lib/src/set_up_mocks.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
void setupFirebaseMocks() {
5+
TestWidgetsFlutterBinding.ensureInitialized();
6+
7+
// ignore: invalid_use_of_visible_for_testing_member
8+
MethodChannelFirebase.channel.setMockMethodCallHandler((call) async {
9+
if (call.method == 'Firebase#initializeCore') {
10+
return [
11+
{
12+
'name': defaultFirebaseAppName,
13+
'options': {
14+
'apiKey': '123',
15+
'appId': '123',
16+
'messagingSenderId': '123',
17+
'projectId': '123',
18+
},
19+
'pluginConstants': {},
20+
}
21+
];
22+
}
23+
24+
if (call.method == 'Firebase#initializeApp') {
25+
return {
26+
'name': call.arguments['appName'],
27+
'options': call.arguments['options'],
28+
'pluginConstants': {},
29+
};
30+
}
31+
return null;
32+
});
33+
}

test/exemples_test.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'package:firebase_database/firebase_database.dart';
2+
import 'package:firebase_database_mocks/firebase_database_mocks.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
class UserRepository {
6+
UserRepository(this.firebaseDatabase);
7+
FirebaseDatabase firebaseDatabase;
8+
9+
Future<String> getUserName(String userId) async {
10+
final userNameReference =
11+
firebaseDatabase.reference().child('users').child(userId).child('name');
12+
final dataSnapshot = await userNameReference.once();
13+
return dataSnapshot.value;
14+
}
15+
16+
Future<Map<String, dynamic>> getUser(String userId) async {
17+
final userNode = firebaseDatabase.reference().child('users/$userId');
18+
final dataSnapshot = await userNode.once();
19+
return dataSnapshot.value;
20+
}
21+
}
22+
23+
void main() {
24+
FirebaseDatabase firebaseDatabase;
25+
UserRepository userRepository;
26+
// Put fake data
27+
const userId = 'userId';
28+
const userName = 'Elon musk';
29+
const fakeData = {
30+
'users': {
31+
userId: {
32+
'name': userName,
33+
'email': '[email protected]',
34+
'photoUrl': 'url-to-photo.jpg',
35+
},
36+
'otherUserId': {
37+
'name': 'userName',
38+
'email': '[email protected]',
39+
'photoUrl': 'other_url-to-photo.jpg',
40+
}
41+
}
42+
};
43+
MockFirebaseDatabase.instance.reference().set(fakeData);
44+
setUp(() {
45+
firebaseDatabase = MockFirebaseDatabase.instance;
46+
userRepository = UserRepository(firebaseDatabase);
47+
});
48+
test('Should get userName ...', () async {
49+
final userNameFromFakeDatabase = await userRepository.getUserName(userId);
50+
expect(userNameFromFakeDatabase, equals(userName));
51+
});
52+
53+
test('Should get user ...', () async {
54+
final userNameFromFakeDatabase = await userRepository.getUser(userId);
55+
expect(
56+
userNameFromFakeDatabase,
57+
equals({
58+
'name': userName,
59+
'email': '[email protected]',
60+
'photoUrl': 'url-to-photo.jpg',
61+
}),
62+
);
63+
});
64+
}

0 commit comments

Comments
 (0)