Skip to content

Commit

Permalink
feature: added the remote data source implementation (data/data_sourc…
Browse files Browse the repository at this point in the history
…es) with test driven development & made the api_keys secret
  • Loading branch information
BasakK6 committed Sep 27, 2023
1 parent bf4007b commit 7437e5a
Show file tree
Hide file tree
Showing 9 changed files with 1,220 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ jobs:
- uses: subosito/flutter-action@v1
with:
channel: "stable"
- name: Decode base64 secrets (api_keys) to lib folder
run: echo $SECRETS_FILE_CONTENT | base64 -d > lib/core/configs/api/api_keys.dart
env:
SECRETS_FILE_CONTENT: ${{ secrets.SECRETS_FILE_CONTENT }}
- run: flutter pub get
- run: flutter analyze
- run: flutter test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

# Sensitive API information
api_keys.dart
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="news_app"
android:name="${applicationName}"
Expand Down
2 changes: 2 additions & 0 deletions lib/core/configs/api/api_configs.dart
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
const String kNewsAPICategoryTechnology = "technology";
const String kNewsAPIBaseURL = "https://newsapi.org/v2/top-headlines";
const String kNewsAPICountry = "us";
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
import 'package:dio/dio.dart';
import 'package:news_app/core/configs/api/api_configs.dart';
import 'package:news_app/core/configs/api/api_keys.dart';
import 'package:news_app/core/error/exceptions.dart';
import 'package:news_app/features/news_feed/data/models/news_feed_model.dart';

abstract class NewsFeedRemoteDataSource {
Future<NewsFeedModel> getNewsFeedForCategory(String category);
}

class NewsFeedRemoteDataSourceImpl extends NewsFeedRemoteDataSource {
final Dio dio;

NewsFeedRemoteDataSourceImpl(this.dio);

@override
Future<NewsFeedModel> getNewsFeedForCategory(String category) async {
final queryParams = {
"country": kNewsAPICountry,
"apiKey": kNewsAPIKey,
"category": category,
};
final response = await dio.get(kNewsAPIBaseURL, queryParameters: queryParams);
if (response.statusCode == 200) {
return NewsFeedModel.fromJson(response.data);
}
throw ServerException();
}
}
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.8"
dio:
dependency: "direct main"
description:
name: dio
sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7"
url: "https://pub.dev"
source: hosted
version: "5.3.3"
equatable:
dependency: "direct main"
description:
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ dependencies:
equatable: ^2.0.5
# internet connection check
connectivity_plus: ^4.0.2
# network requests
dio: ^5.3.3

dev_dependencies:
flutter_test:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:news_app/core/configs/api/api_configs.dart';
import 'package:news_app/core/configs/api/api_keys.dart';
import 'package:news_app/core/error/exceptions.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 '../../../../core/fixtures/fixture_file_reader.dart'
as fixture_file_reader;
import 'news_feed_remote_data_source_test.mocks.dart';

@GenerateNiceMocks([MockSpec<Dio>()])
void main() {
late MockDio mockDio;
late NewsFeedRemoteDataSourceImpl dataSourceImpl;

setUp(() {
mockDio = MockDio();
dataSourceImpl = NewsFeedRemoteDataSourceImpl(mockDio);
});

Future<void> setUpMockDioSuccess200() async {
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
.thenAnswer((realInvocation) async => Response(
data: jsonDecode(fixture_file_reader.fixture("news_feed.json")),
statusCode: 200,
requestOptions: RequestOptions(),
));
}

Future<void> setUpMockDioFailure400() async {
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
.thenAnswer((realInvocation) async => Response(
statusCode: 404,
requestOptions: RequestOptions(),
));
}

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]",
)
],
);

group("getNewsFeedForCategory tests", () {
test(
"should perform a GET request to a URL with specified category, country and apiKey",
() async {
//arrange
//await setUpMockDioSuccess200();
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
.thenAnswer((realInvocation) async => Response(
data: jsonDecode(fixture_file_reader.fixture("news_feed.json")),
statusCode: 200,
requestOptions: RequestOptions(),
));

const baseUrl = "https://newsapi.org/v2/top-headlines";
final queryParams = {
"country": kNewsAPICountry,
"apiKey": kNewsAPIKey,
"category": testCategory,
};
//act
await dataSourceImpl.getNewsFeedForCategory(testCategory);

//assert
verify(mockDio.get(baseUrl, queryParameters: queryParams));
});

test("should return NewsFeedModel when the response code is 200 (success)",
() async {
//arrange
await setUpMockDioSuccess200();
//act
final result = await dataSourceImpl.getNewsFeedForCategory(testCategory);
//assert
expect(result, equals(testNewsFeedModel));
});

test(
"should throw a server exception when the response code is other than 200 (eg: 404)",
() async {
//arrange
await setUpMockDioFailure400();
//act
final calledFunction = dataSourceImpl.getNewsFeedForCategory;
//assert
expect(() => calledFunction(testCategory),
throwsA(const TypeMatcher<ServerException>()));
});
});
}

/*
test("should",(){
//arrange
//act
//assert
});
*/
Loading

0 comments on commit 7437e5a

Please sign in to comment.