[Flutter] : TDD - 08. Local Data Sources
Local Data Base는 앱에 기본적으로 있는 Shared_preferences로 진행한다.
Local DB에는 수많은 옵션들이 있지만
우리는 NumberTrivia 1개를 저장하기 때문에 굳이 다른 것을 쓰지 않고 Shared_preferences
를 사용하기로 했다.
Test
자 TDD 원칙 대로 먼저 test 코드를 작성하자.
class MockSharedPreferences extends Mock implements SharedPreferences {}
void main() {
NumberTriviaLocalDataSourceImpl dataSource;
MockSharedPreferences mockSharedPreferences;
setUp(() {
mockSharedPreferences = MockSharedPreferences();
dataSource = NumberTriviaLocalDataSourceImpl(
sharedPreferences: mockSharedPreferences);
});
}
implementation
number_trivia_local_data_source.dart
abstract class NumberTriviaLocalDataSource {
//cache 데이터를 꺼내간다.
Future<NumberTriviaModel> getLastNumberTrivia();
//cache 데이터를 업데이트 한다.
Future<void> cacheNumberTrivia(NumberTriviaModel tiriviaToCahce);
}
class NumberTriviaLocalDataSourceImpl implements NumberTriviaLocalDataSource {
final SharedPreferences sharedPreferences;
NumberTriviaLocalDataSourceImpl({@required this.sharedPreferences});
@override
Future<void> cacheNumberTrivia(NumberTriviaModel tiriviaToCahce) {
// TODO: implement cacheNumberTrivia
throw null;
}
@override
Future<NumberTriviaModel> getLastNumberTrivia() {
// TODO: implement getLastNumberTrivia
throw null;
}
}
여기서 구현하기 전에 우리가 생각 해봐야 할게 있다.
Shared_Preferences는 String만 저장가능하다.
그렇다면 우리 NumberTrivia 데이터를 어떻게 저장하면 좋을까?
JSON 으로 String화 해서 저장하자.
이건 전에 fixtures 폴더에 구현을 해놓았다.
number_trivia_model.dart
Map<String, dynamic> toJson() {
return {
'text': text,
'number': number,
};
}
number trivia model에도 toJson이 구현이 되어있다.
local_data_source 테스트를 위해 test 폴더 fixtures 폴더 아래
tirivia_cached.json 파일을 만들어주자.
/test/fixtures/trivia_cached.json
{
"text": "Test Text",
"number": 1
}
getLastNumberTrivia
이제 getLastNumberTrivia 함수를 TDD로 개발해보자.
먼저 Test코드다.
number_trivia_local_data_source_test.dart
group('getLastNumberTrivia', () {
final tNumberTriviaModel =
NumberTriviaModel.fromJson(json.decode(fixture('trivia_cached.json')));
test(
'should return NumberTrivia from SharedPreferences when there is one in the cache',
() async {
// arrange
when(mockSharedPreferences.getString(any))
.thenReturn(fixture('trivia_cached.json'));
// act
final result = await dataSource.getLastNumberTrivia();
// assert
verify(mockSharedPreferences.getString('CACHED_NUMBER_TRIVIA'));
expect(result, equals(tNumberTriviaModel));
});
});
이제 test코드를 작성 했으니 test가 돌아가게 구현해보자.
number_trivia_local_data_source.dart
@override
Future<NumberTriviaModel> getLastNumberTrivia() {
final jsonString = sharedPreferences.getString('CACHED_NUMBER_TRIVIA');
// future는 데이터 반환 타입을 맞춰주기 위해 사용
return Future.value(NumberTriviaModel.fromJson(json.decode(jsonString)));
}
위의 데이터 반환 형식과 맞추기 위해 Future.value()를 사용하였다.
그리고 SharedPreferences에서 Key값으로 CACHED_NUMBER_TRIVIA를 사용하는데
Const화 해서 사용하자.
const CACHED_NUMBER_TRIVIA = 'CACHED_NUMBER_TRIVIA';
CacheException
이제 cache 데이터가 없어서 생기는 오류 상황을 처리해보자.
number_trivia_local_data_source_test.dart
test('should throw a CacheException when there is not a cached value', () {
// arrange
when(mockSharedPreferences.getString(any)).thenReturn(null);
// act
final call = dataSource.getLastNumberTrivia;
// assert
expect(() => call(), throwsA(isInstanceOf<CacheException>()));
});
여기서 보면 expect에 평상시랑 다르게 함수나 value를 넣지 않고 Lambda 식으로 함수를 호출 하는 걸 볼 수 있는데 왜냐하면 함수를 실행하는 도중 Exception을 Throw 하기 때문에
call에 value를 넣는게 아니라 function을 넣어서
expect로 함수 호출 도중에 throw를 감지하게 했다.
impl.dart
@override
Future<NumberTriviaModel> getLastNumberTrivia() {
final jsonString = sharedPreferences.getString(CACHED_NUMBER_TRIVIA);
if (jsonString != null) {
// future는 데이터 반환 타입을 맞춰주기 위해 사용
return Future.value(NumberTriviaModel.fromJson(json.decode(jsonString)));
} else {
throw CacheException();
}
}
cacheNumberTrivia
이제 local_data_source의 2번째 함수인 cacheNumberTrivia를 구현해보자.
먼저 테스트코드다.
test.dart
group('cacheNumberTrivia', () {
final tNumberTriviaModel =
NumberTriviaModel(number: 1, text: "test trivia");
test('should call SharedPreferences to cache the data', () {
// act
dataSource.cacheNumberTrivia(tNumberTriviaModel);
// assert
final expectedJsonString = json.encode(tNumberTriviaModel.toJson());
verify(mockSharedPreferences.setString(
CACHED_NUMBER_TRIVIA, expectedJsonString));
});
});
impl.dart
@override
Future<void> cacheNumberTrivia(NumberTriviaModel triviaToCahce) {
return sharedPreferences.setString(
CACHED_NUMBER_TRIVIA, json.encode(triviaToCahce.toJson()));
}
자 테스트도 정상적으로 수행된다.
다음은 Remote data source를 test 및 구현해보자.