Skip to content

fix test #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
24 changes: 24 additions & 0 deletions lib/core/error/exceptions.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
import 'package:dartz/dartz.dart';

import 'failures.dart';

class ServerException implements Exception {}

class CacheException implements Exception {}

typedef Future<Either<Failure, T>> _function<T>();

Future<Either<Failure, T>> catchServerException<T>(
_function<T> functionBody) async {
try {
return await functionBody();
} on ServerException {
return Left(ServerFailure());
}
}

Future<Either<Failure, T>> catchCacheException<T>(
_function<T> functionBody) async {
try {
return await functionBody();
} on CacheException {
return Left(CacheFailure());
}
}
2 changes: 2 additions & 0 deletions lib/core/usecases/usecase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ abstract class UseCase<Type, Params> {
Future<Either<Failure, Type>> call(Params params);
}

// This will be used by the code calling the use case whenever the use case
// doesn't accept any parameters.
class NoParams extends Equatable {
@override
List<Object> get props => [];
Expand Down
6 changes: 5 additions & 1 deletion lib/core/util/input_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ class InputConverter {
Either<Failure, int> stringToUnsignedInteger(String str) {
try {
final integer = int.parse(str);
if (integer < 0) throw FormatException();
if (integer < 0) {
throw FormatException();
}
return Right(integer);
} on ArgumentError {
return Left(InvalidInputFailure());
} on FormatException {
return Left(InvalidInputFailure());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ class NumberTriviaLocalDataSourceImpl implements NumberTriviaLocalDataSource {
final jsonString = sharedPreferences.getString(CACHED_NUMBER_TRIVIA);
if (jsonString != null) {
return Future.value(NumberTriviaModel.fromJson(json.decode(jsonString)));
} else {
throw CacheException();
}
throw CacheException();
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ class NumberTriviaRemoteDataSourceImpl implements NumberTriviaRemoteDataSource {
_getTriviaFromUrl('http://numbersapi.com/random');

Future<NumberTriviaModel> _getTriviaFromUrl(String url) async {
final response = await client.get(
url,
headers: {
'Content-Type': 'application/json',
},
);

if (response.statusCode == 200) {
return NumberTriviaModel.fromJson(json.decode(response.body));
} else {
throw ServerException();
try {
final response = await client.get(
url,
headers: {
'Content-Type': 'application/json',
},
);

if (response.statusCode == 200) {
return NumberTriviaModel.fromJson(json.decode(response.body));
}
} catch (e) {
print(e);
}
throw ServerException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../../domain/repositories/number_trivia_repository.dart';
import '../datasources/number_trivia_local_data_source.dart';
import '../datasources/number_trivia_remote_data_source.dart';

typedef Future<NumberTrivia> _ConcreteOrRandomChooser();
typedef Future<Either<Failure, T>> _ConcreteOrRandomChooser<T>();

class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
final NumberTriviaRemoteDataSource remoteDataSource;
Expand All @@ -26,36 +26,37 @@ class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
int number,
) async {
return await _getTrivia(() {
return remoteDataSource.getConcreteNumberTrivia(number);
return await _handleException(() {
return saveToLocalCache(() => remoteDataSource.getConcreteNumberTrivia(number));
});
}

@override
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() async {
return await _getTrivia(() {
return remoteDataSource.getRandomNumberTrivia();
return await _handleException(() {
return saveToLocalCache(remoteDataSource.getRandomNumberTrivia);
});
}

Future<Either<Failure, NumberTrivia>> _getTrivia(
Future<Either<Failure, NumberTrivia>> _handleException(
_ConcreteOrRandomChooser getConcreteOrRandom,
) async {
if (await networkInfo.isConnected) {
try {
final remoteTrivia = await getConcreteOrRandom();
localDataSource.cacheNumberTrivia(remoteTrivia);
return Right(remoteTrivia);
} on ServerException {
return Left(ServerFailure());
}
} else {
try {
final localTrivia = await localDataSource.getLastNumberTrivia();
return Right(localTrivia);
} on CacheException {
return Left(CacheFailure());
}
return catchServerException(getConcreteOrRandom);
}
return catchCacheException(_getLastNumberTriviaInCache);
}

Future<Either<Failure, NumberTrivia>> saveToLocalCache(
getConcreteOrRandom,
) async {
final remoteTrivia = await getConcreteOrRandom();
localDataSource.cacheNumberTrivia(remoteTrivia);
return Right(remoteTrivia);
}

Future<Either<Failure, NumberTrivia>> _getLastNumberTriviaInCache() async {
final localTrivia = await localDataSource.getLastNumberTrivia();
return Right(localTrivia);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:clean_architecture_tdd_course/core/error/failures.dart';
import 'package:clean_architecture_tdd_course/core/usecases/usecase.dart';
import 'package:clean_architecture_tdd_course/features/number_trivia/domain/entities/number_trivia.dart';
Expand Down Expand Up @@ -39,6 +39,8 @@ class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> {
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
// Immediately branching the logic with type checking, in order
// for the event to be smart casted
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
Expand Down
12 changes: 12 additions & 0 deletions test/core/util/input_converter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ void main() {
},
);

test(
'should return a Failure when the string is null',
() async {
// arrange
final str = null;
// act
final result = inputConverter.stringToUnsignedInteger(str);
// assert
expect(result, Left(InvalidInputFailure()));
},
);

test(
'should return a Failure when the string is not an integer',
() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void main() {
);

test(
'should throw a CacheExeption when there is not a cached value',
'should throw a CacheException when there is not a cached value',
() async {
// arrange
when(mockSharedPreferences.getString(any)).thenReturn(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ void main() {
NumberTriviaRemoteDataSourceImpl dataSource;
MockHttpClient mockHttpClient;

final tNumberTriviaModel =
NumberTriviaModel.fromJson(json.decode(fixture('trivia.json')));

setUp(() {
mockHttpClient = MockHttpClient();
dataSource = NumberTriviaRemoteDataSourceImpl(client: mockHttpClient);
Expand All @@ -26,15 +29,18 @@ void main() {
.thenAnswer((_) async => http.Response(fixture('trivia.json'), 200));
}

void setUpMockHttpClientSuccess200Infinity() {
when(mockHttpClient.get(any, headers: anyNamed('headers')))
.thenAnswer((_) async => http.Response(fixture('trivia_infinity.json'), 200));
}

void setUpMockHttpClientFailure404() {
when(mockHttpClient.get(any, headers: anyNamed('headers')))
.thenAnswer((_) async => http.Response('Something went wrong', 404));
}

group('getConcreteNumberTrivia', () {
final tNumber = 1;
final tNumberTriviaModel =
NumberTriviaModel.fromJson(json.decode(fixture('trivia.json')));

test(
'''should perform a GET request on a URL with number
Expand Down Expand Up @@ -80,12 +86,9 @@ void main() {
});

group('getRandomNumberTrivia', () {
final tNumberTriviaModel =
NumberTriviaModel.fromJson(json.decode(fixture('trivia.json')));

test(
'''should perform a GET request on a URL with number
being the endpoint and with application/json header''',
'''should perform a GET request on a URL with *random* endpoint
with application/json header''',
() async {
// arrange
setUpMockHttpClientSuccess200();
Expand Down Expand Up @@ -113,6 +116,19 @@ void main() {
},
);

test(
'''should throw a ServerException when the response code is 200 (success)
but the Trivia number is null (infinity)''',
() async {
// arrange
setUpMockHttpClientSuccess200Infinity();
// act
final call = dataSource.getRandomNumberTrivia;
// assert
expect(() => call(), throwsA(TypeMatcher<ServerException>()));
},
);

test(
'should throw a ServerException when the response code is 404 or other',
() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ void main() {
mockRemoteDataSource = MockRemoteDataSource();
mockLocalDataSource = MockLocalDataSource();
mockNetworkInfo = MockNetworkInfo();

repository = NumberTriviaRepositoryImpl(
remoteDataSource: mockRemoteDataSource,
localDataSource: mockLocalDataSource,
Expand Down Expand Up @@ -77,7 +78,7 @@ void main() {
'should return remote data when the call to remote data source is successful',
() async {
// arrange
when(mockRemoteDataSource.getConcreteNumberTrivia(any))
when(mockRemoteDataSource.getConcreteNumberTrivia(tNumber))
.thenAnswer((_) async => tNumberTriviaModel);
// act
final result = await repository.getConcreteNumberTrivia(tNumber);
Expand All @@ -91,7 +92,7 @@ void main() {
'should cache the data locally when the call to remote data source is successful',
() async {
// arrange
when(mockRemoteDataSource.getConcreteNumberTrivia(any))
when(mockRemoteDataSource.getConcreteNumberTrivia(tNumber))
.thenAnswer((_) async => tNumberTriviaModel);
// act
await repository.getConcreteNumberTrivia(tNumber);
Expand All @@ -105,7 +106,7 @@ void main() {
'should return server failure when the call to remote data source is unsuccessful',
() async {
// arrange
when(mockRemoteDataSource.getConcreteNumberTrivia(any))
when(mockRemoteDataSource.getConcreteNumberTrivia(tNumber))
.thenThrow(ServerException());
// act
final result = await repository.getConcreteNumberTrivia(tNumber);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:clean_architecture_tdd_course/core/usecases/usecase.dart';
import 'package:clean_architecture_tdd_course/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_course/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_course/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart';
import 'package:clean_architecture_tdd_course/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:mockito/mockito.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ void main() {
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Right(tNumberParsed));

void setUpMockGetConcreteNumberTriviaSuccess() =>
when(mockGetConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));

test(
'should call the InputConverter to validate and convert the string to an unsigned integer',
() async {
// arrange
setUpMockInputConverterSuccess();
// MEMO: need add this, or Unhandled error NoSuchMethodError: The method 'fold' was called on null.
setUpMockGetConcreteNumberTriviaSuccess();

// act
bloc.add(GetTriviaForConcreteNumber(tNumberString));
await untilCalled(mockInputConverter.stringToUnsignedInteger(any));
Expand Down Expand Up @@ -83,8 +90,7 @@ void main() {
() async {
// arrange
setUpMockInputConverterSuccess();
when(mockGetConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
setUpMockGetConcreteNumberTriviaSuccess();
// act
bloc.add(GetTriviaForConcreteNumber(tNumberString));
await untilCalled(mockGetConcreteNumberTrivia(any));
Expand All @@ -98,8 +104,8 @@ void main() {
() async {
// arrange
setUpMockInputConverterSuccess();
when(mockGetConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
setUpMockGetConcreteNumberTriviaSuccess();

// assert later
final expected = [
Empty(),
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/trivia_infinity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"text": "Test Text",
"number": null,
"found": true,
"type": "trivia"
}