Skip to content

Commit

Permalink
created the repository implementation in the data layer with test dri…
Browse files Browse the repository at this point in the history
…ven development (news feed feature)
  • Loading branch information
BasakK6 committed Sep 26, 2023
1 parent 9510747 commit 198c8d0
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/core/error/exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ServerException implements Exception {}

class ConnectivityException implements Exception {}
5 changes: 5 additions & 0 deletions lib/core/error/failures.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ abstract class Failure extends Equatable {
return properties != null ? [properties] : [];
}
}

// concrete failure classes that map exceptions 1 to 1
class ServerFailure extends Failure {}

class ConnectivityFailure extends Failure {}
3 changes: 3 additions & 0 deletions lib/core/platform/network_info.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
abstract class NetworkInfo {
Future<bool> checkConnection();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:news_app/features/news_feed/data/models/news_feed_model.dart';

abstract class NewsFeedRemoteDataSource {
Future<NewsFeedModel> getNewsFeedForCategory(String category);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:dartz/dartz.dart';
import 'package:news_app/core/error/exceptions.dart';
import 'package:news_app/core/error/failures.dart';
import 'package:news_app/core/platform/network_info.dart';
import 'package:news_app/features/news_feed/data/data_sources/news_feed_remote_data_source.dart';
import 'package:news_app/features/news_feed/domain/entities/news_feed_entity.dart';
import 'package:news_app/features/news_feed/domain/repositories/news_feed_repository.dart';

class NewsFeedRepositoryImpl extends NewsFeedRepository {
final NetworkInfo networkInfo;
final NewsFeedRemoteDataSource remoteDataSource;

NewsFeedRepositoryImpl(
{required this.networkInfo, required this.remoteDataSource});

@override
Future<Either<Failure, NewsFeedEntity>> getNewsFeed(String category) async {
try {
await networkInfo.checkConnection();
final NewsFeedEntity remoteTrivia =
await remoteDataSource.getNewsFeedForCategory(category);
// can also cache the data when the remote fetch is successful
// and return the cached value if there is no network connection
return Right(remoteTrivia);
} on ConnectivityException {
return Left(ConnectivityFailure());
} on ServerException {
return Left(ServerFailure());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:news_app/core/error/exceptions.dart';
import 'package:news_app/core/error/failures.dart';
import 'package:news_app/core/platform/network_info.dart';
import 'package:news_app/features/news_feed/data/data_sources/news_feed_remote_data_source.dart';
import 'package:news_app/features/news_feed/data/models/news_feed_model.dart';
import 'package:news_app/features/news_feed/data/repositories/news_feed_repository_impl.dart';
import 'package:news_app/features/news_feed/domain/entities/news_feed_entity.dart';

import '../../../../core/fixtures/api_configs.dart';
import 'news_feed_repository_impl_test.mocks.dart';

//mock the abstract network info (core/platform)
@GenerateNiceMocks([MockSpec<NetworkInfo>()])
//mock the abstract remote data source
@GenerateNiceMocks([MockSpec<NewsFeedRemoteDataSource>()])
void main() {
late MockNetworkInfo mockNetworkInfo;
late MockNewsFeedRemoteDataSource mockNewsFeedRemoteDataSource;
late NewsFeedRepositoryImpl repositoryImpl;

setUp(() {
mockNetworkInfo = MockNetworkInfo();
mockNewsFeedRemoteDataSource = MockNewsFeedRemoteDataSource();
repositoryImpl = NewsFeedRepositoryImpl(
remoteDataSource: mockNewsFeedRemoteDataSource,
networkInfo: mockNetworkInfo,
);
});

const testCategory = kNewsAPICategoryTechnology;
const testNewsFeedModel = NewsFeedModel(
status: "ok",
totalResults: 1,
articles: [
ArticlesModel(
source: SourceModel(
name: "Nintendo Life",
),
author: "Liam Doolan",
title:
"Metal Gear Solid: Master Collection Vol. 1 Resolution & Frame Rate Chart Released - Nintendo Life",
description: "Here's how the Switch version compares",
url:
"https://www.nintendolife.com/news/2023/09/metal-gear-solid-master-collection-vol-1-resolution-and-frame-rate-chart-released",
urlToImage:
"https://images.nintendolife.com/9ae5106942e8a/1280x720.jpg",
publishedAt: "2023-09-24T05:35:00Z",
content:
"Image: Konami\r\nHideo Kojima's legendary series Metal Gear Solid returns as a collection release this October on all platforms including the Nintendo Switch, and as part of this Konami has now shared … [+1295 chars]",
)
],
);
const NewsFeedEntity testNewsFeedEntity = testNewsFeedModel;

group("getNewsFeed tests", () {
test('should check if the device is online', () async {
//arrange
when(mockNetworkInfo.checkConnection()).thenAnswer((_) async => true);
// act
await repositoryImpl.getNewsFeed(testCategory);
// assert
verify(mockNetworkInfo.checkConnection());
});

group("device is online", () {
setUp(() async {
//always return true for this group
when(mockNetworkInfo.checkConnection()).thenAnswer((_) async => true);
});

test(
'should return remote data when the call to remote data source is successful',
() async {
// arrange
when(mockNewsFeedRemoteDataSource
.getNewsFeedForCategory(testCategory))
.thenAnswer((_) async => testNewsFeedModel);
// act
final result = await repositoryImpl.getNewsFeed(testCategory);
// assert
verify(mockNewsFeedRemoteDataSource
.getNewsFeedForCategory(testCategory));
expect(result, equals(const Right(testNewsFeedEntity)));
},
);

test(
'should return ServerFailure when the call to remote data source is unsuccessful',
() async {
// arrange
when(mockNewsFeedRemoteDataSource
.getNewsFeedForCategory(testCategory))
.thenThrow(ServerException());
// act
final result = await repositoryImpl.getNewsFeed(testCategory);
// assert
verify(mockNewsFeedRemoteDataSource
.getNewsFeedForCategory(testCategory));
expect(result, equals(Left(ServerFailure())));
},
);
});

group("device is offline", () {
setUp(() async {
//always return false for this group
when(mockNetworkInfo.checkConnection())
.thenThrow(ConnectivityException());
});

test(
"should return Connectivity Failure when there is no internet connection",
() async {
//arrange

//act
final result = await repositoryImpl.getNewsFeed(testCategory);
//assert
verifyZeroInteractions(mockNewsFeedRemoteDataSource);
expect(result, Left(ConnectivityFailure()));
});
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Mocks generated by Mockito 5.4.2 from annotations
// in news_app/test/features/news_feed/data/repositories/news_feed_repository_impl_test.dart.
// Do not manually edit this file.

// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i4;

import 'package:mockito/mockito.dart' as _i1;
import 'package:news_app/core/platform/network_info.dart' as _i3;
import 'package:news_app/features/news_feed/data/data_sources/news_feed_remote_data_source.dart'
as _i5;
import 'package:news_app/features/news_feed/data/models/news_feed_model.dart'
as _i2;

// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class

class _FakeNewsFeedModel_0 extends _i1.SmartFake implements _i2.NewsFeedModel {
_FakeNewsFeedModel_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}

/// A class which mocks [NetworkInfo].
///
/// See the documentation for Mockito's code generation for more information.
class MockNetworkInfo extends _i1.Mock implements _i3.NetworkInfo {
@override
_i4.Future<bool> checkConnection() => (super.noSuchMethod(
Invocation.method(
#checkConnection,
[],
),
returnValue: _i4.Future<bool>.value(false),
returnValueForMissingStub: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);
}

/// A class which mocks [NewsFeedRemoteDataSource].
///
/// See the documentation for Mockito's code generation for more information.
class MockNewsFeedRemoteDataSource extends _i1.Mock
implements _i5.NewsFeedRemoteDataSource {
@override
_i4.Future<_i2.NewsFeedModel> getNewsFeedForCategory(String? category) =>
(super.noSuchMethod(
Invocation.method(
#getNewsFeedForCategory,
[category],
),
returnValue: _i4.Future<_i2.NewsFeedModel>.value(_FakeNewsFeedModel_0(
this,
Invocation.method(
#getNewsFeedForCategory,
[category],
),
)),
returnValueForMissingStub:
_i4.Future<_i2.NewsFeedModel>.value(_FakeNewsFeedModel_0(
this,
Invocation.method(
#getNewsFeedForCategory,
[category],
),
)),
) as _i4.Future<_i2.NewsFeedModel>);
}

0 comments on commit 198c8d0

Please sign in to comment.