{"id":24491696,"url":"https://github.com/asif-faizal/informed","last_synced_at":"2025-05-08T20:54:26.068Z","repository":{"id":269912686,"uuid":"908602510","full_name":"Asif-Faizal/Informed","owner":"Asif-Faizal","description":"Informed is a News app built with Flutter's Test-Driven Development (TDD) and Clean Architecture with 49 tests. It focuses on modularity, testability, and maintainability by organizing the app into distinct layers: presentation, domain, and data. The project ensures high code quality through comprehensive testing and best practices.","archived":false,"fork":false,"pushed_at":"2024-12-30T11:37:49.000Z","size":770,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T18:07:34.776Z","etag":null,"topics":["bloc","clean-architecture","ui-testing","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Asif-Faizal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-26T13:29:56.000Z","updated_at":"2025-01-08T13:29:54.000Z","dependencies_parsed_at":"2024-12-28T12:17:55.218Z","dependency_job_id":null,"html_url":"https://github.com/Asif-Faizal/Informed","commit_stats":null,"previous_names":["asif-faizal/flutter-tdd","asif-faizal/informed"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asif-Faizal%2FInformed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asif-Faizal%2FInformed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asif-Faizal%2FInformed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asif-Faizal%2FInformed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Asif-Faizal","download_url":"https://codeload.github.com/Asif-Faizal/Informed/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253149380,"owners_count":21861717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bloc","clean-architecture","ui-testing","unit-testing"],"created_at":"2025-01-21T18:19:01.288Z","updated_at":"2025-05-08T20:54:26.034Z","avatar_url":"https://github.com/Asif-Faizal.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Informed        \u003cimg src=\"https://github.com/user-attachments/assets/cb71cef2-1e3b-4b8e-a7a0-e338fb1705d1\" width=\"30\" height=\"30\" /\u003e\n\n\nInformed is a News app built with Flutter's Test-Driven Development (TDD) and Clean Architecture with 49 tests. It focuses on modularity, testability, scalability and maintainability.\n\n![Untitled design](https://github.com/user-attachments/assets/dcd5afae-fa1f-4ddd-8159-5b3ad1b1db93)\n\n## API\n### URLs\nNews by Query\n```bash\nhttps://newsapi.org/v2/everything?q=query\u0026sortBy=publishedAt\u0026apiKey=API_KEY\n```\nNews by Country and Category\n```bash\nhttps://newsapi.org/v2/top-headlines?country=us\u0026category=business\u0026apiKey=API_KEY\n```\n\n### Response\n```json\n{\n    \"status\": \"ok\",\n    \"totalResults\": 2,\n    \"articles\": [\n        {\n            \"source\": {\n                \"id\": \"associated-press\",\n                \"name\": \"Associated Press\"\n            },\n            \"author\": \"BRIAN P. D. HANNON\",\n            \"title\": \"Charles Dolan, HBO and Cablevision founder, dies at 98 - The Associated Press\",\n            \"description\": \"Charles Dolan, who founded some of the most prominent U.S. media companies including Home Box Office Inc. and Cablevision Systems Corp., has died at age 98. Newsday reports that a statement issued Saturday by his family says Dolan died of natural causes. Dola…\",\n            \"url\": \"https://apnews.com/article/charles-dolan-dies-obituary-hbo-cablevision-bc5b48318f336f633b7df006afcd60c4\",\n            \"urlToImage\": \"https://dims.apnews.com/dims4/default/f2a2365/2147483647/strip/true/crop/4364x2455+0+291/resize/1440x810!/quality/90/?url=https%3A%2F%2Fassets.apnews.com%2F37%2F90%2F56bc4c057c97e40ed424ca4dea58%2F9b635bd1d71340b9998b6cf1d63e1bd5\",\n            \"publishedAt\": \"2024-12-29T04:39:00Z\",\n            \"content\": \"Charles Dolan, who founded some of the most prominent U.S. media companies including Home Box Office Inc. and Cablevision Systems Corp., has died at age 98, according to a news report.\\r\\nA statement i… [+1269 chars]\"\n        },\n        {\n            \"source\": {\n                \"id\": null,\n                \"name\": \"[Removed]\"\n            },\n            \"author\": null,\n            \"title\": \"[Removed]\",\n            \"description\": \"[Removed]\",\n            \"url\": \"https://removed.com\",\n            \"urlToImage\": null,\n            \"publishedAt\": \"2024-12-28T22:09:33Z\",\n            \"content\": \"[Removed]\"\n        },\n    ]\n}\n```\n\n## Entity\n\n```dart\nimport 'package:equatable/equatable.dart';\n\nclass NewsEntity extends Equatable {\n  final String? sourceId;\n  final String sourceName;\n  final String? author;\n  final String title;\n  final String? description;\n  final String url;\n  final String? urlToImage;\n  final DateTime publishedAt;\n  final String? content;\n\n  const NewsEntity({\n    required this.sourceId,\n    required this.sourceName,\n    required this.author,\n    required this.title,\n    required this.description,\n    required this.url,\n    required this.urlToImage,\n    required this.publishedAt,\n    required this.content,\n  });\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [\n        sourceId,\n        sourceName,\n        author,\n        title,\n        description,\n        url,\n        urlToImage,\n        publishedAt,\n        content,\n      ];\n}\n```\n\n## Failures\n```dart\nabstract class Failure extends Equatable {\n  const Failure();\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [];\n}\n\n// Server failure class\nclass ServerFailure extends Failure {\n  final String message;\n\n  const ServerFailure(this.message);\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [message];\n}\n\n// Cache failure class\nclass CacheFailure extends Failure {\n  final String message;\n\n  const CacheFailure(this.message);\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [message];\n}\n```\n\n## Repo\nFrom the entity equest and response model neede to be tested\nRepo with either failure or the entity is created using `dartz`\n```dart\nimport 'package:dartz/dartz.dart';\n\nimport '../../../core/error/failures.dart';\nimport 'news_entity.dart';\n\nabstract class NewsRepo {\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e getQueryNews(String query);\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e getCountryNews(String country, String category);\n}\n```\n\n## Usecases\nA simple usecase class is made initially\n```dart\nclass GetQueryNews {\n  final NewsRepo repository;\n\n  GetQueryNews(this.repository);\n}\n```\n### UseCase Test\nFor mocking the repo\n```dart\n@GenerateMocks([NewsRepo])\n\nvoid main(){}\n```\nand `build_runner` is initiated to create `MockNewsRepo`\n```bash\nflutter pub run build_runner build\n```\nTo the main function\n```dart\nvoid main() {\n  late GetCountryNews getCountryNews; // Declaring a variable for the GetCountryNews use case.\n  late GetQueryNews getQueryNews; // Declaring a variable for the GetQueryNews use case.\n  late MockNewsRepo mockNewsRepo; // Declaring a variable for the mocked NewsRepo.\n\n  // This is the setup method, which runs before each test.\n  setUp(() {\n    mockNewsRepo = MockNewsRepo(); // Initializing the mocked NewsRepo.\n    getQueryNews = GetQueryNews(mockNewsRepo); // Initializing the use case with the mocked repository.\n    getCountryNews = GetCountryNews(mockNewsRepo); // Initializing the use case with the mocked repository.\n  });\n}\n```\nTest Input and Output for Mocking\n```dart\n  // Example news entity that will be used in the test.\nfinal tNewsEntityList = [\n    NewsEntity(\n      sourceId: '1',\n      sourceName: 'Test Source',\n      author: 'Test Author',\n      title: 'Test Title 1',\n      description: 'Test Description 1',\n      url: 'https://example.com',\n      urlToImage: 'https://example.com/image1.jpg',\n      publishedAt: DateTime.now(),\n      content: 'Test Content 1',\n    ),\n    NewsEntity(\n      sourceId: '2',\n      sourceName: 'Test Source 2',\n      author: 'Test Author 2',\n      title: 'Test Title 2',\n      description: 'Test Description 2',\n      url: 'https://example.com',\n      urlToImage: 'https://example.com/image2.jpg',\n      publishedAt: DateTime.now(),\n      content: 'Test Content 2',\n    ),\n  ];\n\n  const tQuery = 'test query'; // A test query to search news with.\n  const tCountry = 'test Country'; // A test country to search news with.\n  const tCategory = 'test Category'; // A test category to search news with.\n```\nInside a group test success and failure tests should be performed\n```dart\n    test(\n      'should return a list of NewsEntities when the repository call is successful',\n      () async {\n        // Arranging the mock to return a successful response (Right).\n        when(mockNewsRepo.getQueryNews(tQuery))\n            .thenAnswer((_) async =\u003e Right(tNewsEntityList));\n\n        // Act: Calling the use case's call method with the test query.\n        final result = await getQueryNews.call(tQuery);\n\n        // Assert: Verifying the result and the expected outcome.\n        expect(result, Right(tNewsEntityList)); // Should return the list of NewsEntities.\n        verify(mockNewsRepo.getQueryNews(tQuery)); // Verifying that the repository's method was called with the correct query.\n        verifyNoMoreInteractions(mockNewsRepo); // Verifying that no other interactions occurred with the mock.\n      },\n    );\n```\n```dart\n    test(\n      'should return ServerFailure when the repository call is unsuccessful',\n      () async {\n        // Arranging the mock to return a failure (Left).\n        when(mockNewsRepo.getQueryNews(tQuery))\n            .thenAnswer((_) async =\u003e Left(ServerFailure('Error')));\n\n        // Act: Calling the use case's call method with the test query.\n        final result = await getQueryNews.call(tQuery);\n\n        // Assert: Verifying the result and the expected failure outcome.\n        expect(result, Left(ServerFailure('Error'))); // Should return a ServerFailure.\n        verify(mockNewsRepo.getQueryNews(tQuery)); // Verifying that the repository's method was called with the correct query.\n        verifyNoMoreInteractions(mockNewsRepo); // Verifying that no other interactions occurred with the mock.\n      },\n    );\n```\nSimiliar test should be done for `GetCountryNews`\n\nalso Params to be used in UseCase\n```dart\nclass GetQueryNewsParams extends Equatable {\n  final String query;\n\n  GetQueryNewsParams({required this.query});\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [query];\n}\n```\nParams in the test \n```dart\nfinal result = await getQueryNews.call(GetQueryNewsParams(query: tQuery));\n```\nFinal Usecases\n```dart\nclass GetQueryNews implements Usecase\u003cList\u003cNewsEntity\u003e, GetQueryNewsParams\u003e {\n  final NewsRepo repository;\n\n  GetQueryNews(this.repository);\n\n  @override\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e call(GetQueryNewsParams params) {\n    return repository.getQueryNews(params.query);\n  }\n}\n\nclass GetQueryNewsParams extends Equatable {\n  final String query;\n\n  GetQueryNewsParams({required this.query});\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [query];\n}\n```\n```dart\nclass GetCountryNews implements Usecase\u003cList\u003cNewsEntity\u003e, GetCountryNewsParams\u003e {\n  final NewsRepo repository;\n\n  GetCountryNews(this.repository);\n\n  @override\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e call(GetCountryNewsParams params) {\n    return repository.getCountryNews(params.country, params.category);\n  }\n}\n\nclass GetCountryNewsParams extends Equatable {\n  final String country;\n  final String category;\n\n  GetCountryNewsParams({required this.country, required this.category});\n\n  @override\n  List\u003cObject?\u003e get props =\u003e [country, category];\n}\n```\nNow the Model can be made\n\n## Model\n```dart\nclass NewsModel extends NewsEntity {\n  const NewsModel({\n    required String? sourceId,\n    required String sourceName,\n    required String? author,\n    required String title,\n    required String? description,\n    required String url,\n    required String? urlToImage,\n    required DateTime publishedAt,\n    required String? content,\n  }) : super(\n          sourceId: sourceId,\n          sourceName: sourceName,\n          author: author,\n          title: title,\n          description: description,\n          url: url,\n          urlToImage: urlToImage,\n          publishedAt: publishedAt,\n          content: content,\n        );\n}\n```\n### Model Test\nAll type of response need to be tested with nullable response and non nullable responses\n\nCheck if Model is SubType of Entity\n```dart\nvoid main() {\n  group('NewsModel tests', () {\n    final tNewsModel1 = NewsModel(\n      sourceId: null,\n      sourceName: 'Reuters',\n      author: 'Reuters',\n      title:\n          'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n      description: null,\n      url:\n          'https://www.reuters.com/world/asia-pacific/passenger-plane-crashes-kazakhstan-emergencies-ministry-says-2024-12-25/',\n      urlToImage: null,\n      publishedAt: DateTime.parse('2024-12-25T08:19:37Z'),\n      content: null,\n    );\n    final tNewsModel2 = NewsModel(\n      sourceId: '16723541',\n      sourceName: 'Reuters',\n      author: 'Reuters',\n      title:\n          'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n      description: 'Passenger plane crashed',\n      url:\n          'https://www.reuters.com/world/asia-pacific/passenger-plane-crashes-kazakhstan-emergencies-ministry-says-2024-12-25/',\n      urlToImage: 'http://example.com',\n      publishedAt: DateTime.parse('2024-12-25T08:19:37Z'),\n      content: 'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n    );\n\n    test('Should be a subclass of NewsEntity for nullable response', () async {\n      // arrange\n      expect(tNewsModel1, isA\u003cNewsEntity\u003e());\n    });\n\n    test('Should be a subclass of NewsEntity for non-nullable response', () async {\n      // arrange\n      expect(tNewsModel2, isA\u003cNewsEntity\u003e());\n    });\n  });\n}\n```\n### Test for fromJson\nA json reader needs to be created to read .json files from the project directory.\n\n```dart\nimport 'dart:io';\n\nString fixture(String name) =\u003e File('test/fixtures/$name').readAsStringSync();\n```\nAnd store the types of json responses in a .json file\n\n```dart\n    test('fromJson method for nullable response', () async {\n      //arrange\n      final Map\u003cString, dynamic\u003e jsonMap =\n          json.decode(fixture('news_null.json'));\n      //act\n      final result = NewsModel.fromJson(jsonMap);\n      //assert\n      expect(result, tNewsModel1);\n    });\n\n    test('fromJson method for non nullable response', () async {\n      //arrange\n      final Map\u003cString, dynamic\u003e jsonMap =\n          json.decode(fixture('news_non_null.json'));\n      //act\n      final result = NewsModel.fromJson(jsonMap);\n      //assert\n      expect(result, tNewsModel2);\n    });\n```\nformed fromJson\n```dart\n  factory NewsModel.fromJson(Map\u003cString, dynamic\u003e json) {\n    return NewsModel(\n      sourceId: json['source']?['id'] as String?,\n      sourceName: json['source']?['name'] as String? ?? '',\n      author: json['author'] as String?,\n      title: json['title'] as String,\n      description: json['description'] as String?,\n      url: json['url'] as String,\n      urlToImage: json['urlToImage'] as String?,\n      publishedAt: DateTime.parse(json['publishedAt'] as String),\n      content: json['content'] as String?,\n    );\n  }\n```\n\n### Test for `toJson` (optional)\n\n```dart\n    test('toJson method for nullable response', () async {\n      // arrange: converting the NewsModel object to a JSON string with indentation\n      final result = JsonEncoder.withIndent('    ').convert(tNewsModel1.toJson());\n      // expected JSON string from the fixture file\n      final expectedJson = fixture('news_null.json');\n      \n      // assert: checking if the result matches the expected JSON\n      expect(result, expectedJson);\n    });\n\n    // Test case to verify the toJson method for non-nullable response\n    test('toJson method for non nullable response', () async {\n      // arrange: converting the NewsModel object to a JSON string with indentation\n      final result = JsonEncoder.withIndent('    ').convert(tNewsModel2.toJson());\n      // expected JSON string from the fixture file\n      final expectedJson = fixture('news_non_null.json');\n      \n      // assert: checking if the result matches the expected JSON\n      expect(result, expectedJson);\n    });\n```\nformed toJson\n```dart\n  Map\u003cString, dynamic\u003e toJson() {\n    return {\n      'source': {\n        'id': sourceId,\n        'name': sourceName,\n      },\n      'author': author,\n      'title': title,\n      'description': description,\n      'url': url,\n      'urlToImage': urlToImage,\n      'publishedAt': publishedAt.toUtc().toIso8601String().replaceAll('.000Z', 'Z'),\n      'content': content,\n    };\n  }\n```\n\n## Initialising Datasource\nRemote Datasource\n```dart\nabstract class NewsRemoteDatasource {\n  // calls API [https://newsapi.org/v2/top-headlines?country=us\u0026category=business\u0026apiKey=API_KEY]\n  Future\u003cList\u003cNewsModel\u003e\u003e getQueryNews(String query);\n  // calls API [https://newsapi.org/v2/everything?q=apple\u0026sortBy=publishedAt\u0026apiKey=API_KEY]\n  Future\u003cList\u003cNewsModel\u003e\u003e getCountryNews(String country, String category);\n}\n```\n\nLocal Datasource\n```dart\nabstract class NewsLocalDatasource {\n  Future\u003cList\u003cNewsModel\u003e\u003e getLastNews();\n  Future\u003cvoid\u003e cacheNews(List\u003cNewsModel\u003e newsToCache);\n}\n```\n\n### Network Info\nUsed to detect weather to fetch from Remote or Local Datasource\n```dart\nabstract class NetworkInfo {\n  Future\u003cbool\u003e get isConnected;\n}\n```\n```dart\n@GenerateMocks([Connectivity])\n```\n\nTest for checking the package behaves correctly\n```dart\ngroup('Mocked Connectivity Tests', () {\n    late MockConnectivity mockConnectivity;\n\n    setUp(() {\n      mockConnectivity = MockConnectivity();\n\n      // Initialize the mock method to return a default value\n      when(mockConnectivity.checkConnectivity()).thenAnswer(\n        (_) async =\u003e [ConnectivityResult.none],\n      );\n    });\n\n    test('Check WiFi Connectivity with Mock', () async {\n      // Mock the return value to simulate WiFi connection\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.wifi]);\n\n      // Call the method and assert the result\n      final result = await mockConnectivity.checkConnectivity();\n      expect(result, [ConnectivityResult.wifi]);\n    });\n\n    test('Check Mobile Data Connectivity with Mock', () async {\n      // Mock the return value to simulate mobile data connection\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.mobile]);\n\n      // Call the method and assert the result\n      final result = await mockConnectivity.checkConnectivity();\n      expect(result, [ConnectivityResult.mobile]);\n    });\n\n    test('Check No Connectivity with Mock', () async {\n      // Mock the return value to simulate no connectivity\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.none]);\n\n      // Call the method and assert the result\n      final result = await mockConnectivity.checkConnectivity();\n      expect(result, [ConnectivityResult.none]);\n    });\n\n    test('Check Connectivity Change Listener with Mock', () async {\n      // Create a stream controller that emits lists of connectivity results\n      final streamController = StreamController\u003cList\u003cConnectivityResult\u003e\u003e();\n\n      // Mock the connectivity change stream\n      when(mockConnectivity.onConnectivityChanged)\n          .thenAnswer((_) =\u003e streamController.stream);\n\n      // Add test values to the stream as lists\n      streamController.add([ConnectivityResult.wifi]);\n      streamController\n          .add([ConnectivityResult.wifi, ConnectivityResult.mobile]);\n      streamController.add([ConnectivityResult.none]);\n\n      // Listen for connectivity changes and assert the values\n      await expectLater(\n        mockConnectivity.onConnectivityChanged,\n        emitsInOrder([\n          [ConnectivityResult.wifi],\n          [ConnectivityResult.wifi, ConnectivityResult.mobile],\n          [ConnectivityResult.none]\n        ]),\n      );\n\n      // Clean up\n      await streamController.close();\n    });\n  });\n```\n\nTest for `NetworkInfo Implementation`\n```dart\n group('Test connectivity on Network Info Implementation', () {\n    late MockConnectivity mockConnectivity;\n    late NetworkInfoImpl networkInfoImpl;\n\n    setUp(() {\n      mockConnectivity = MockConnectivity();\n      networkInfoImpl = NetworkInfoImpl(connectivity: mockConnectivity);\n    });\n\n    test('Should return true when connectivity result is wifi', () async {\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.wifi]);\n\n      final result = await networkInfoImpl.isConnected;\n\n      expect(result, true);\n    });\n\n    test('Should return true when connectivity result is mobile', () async {\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.mobile]);\n\n      final result = await networkInfoImpl.isConnected;\n\n      expect(result, true);\n    });\n\n    test('Should return false when connectivity result is only none', () async {\n      // Arrange\n      when(mockConnectivity.checkConnectivity())\n          .thenAnswer((_) async =\u003e [ConnectivityResult.none]);\n\n      // Act\n      final result = await networkInfoImpl.isConnected;\n\n      // Assert\n      expect(result, false);\n      verify(mockConnectivity.checkConnectivity());\n    });\n\n    test(\n        'Should return true when multiple connectivity results include a valid connection',\n        () async {\n      // Arrange\n      when(mockConnectivity.checkConnectivity()).thenAnswer(\n          (_) async =\u003e [ConnectivityResult.none, ConnectivityResult.wifi]);\n\n      // Act\n      final result = await networkInfoImpl.isConnected;\n\n      // Assert\n      expect(result, true);\n      verify(mockConnectivity.checkConnectivity());\n    });\n  });\n```\nNetworkInfo Implementation can be created \n```dart\nabstract class NetworkInfo {\n  Future\u003cbool\u003e get isConnected;\n}\n\nclass NetworkInfoImpl implements NetworkInfo {\n  final Connectivity connectivity;\n\n  NetworkInfoImpl({required this.connectivity});\n\n  @override\n  Future\u003cbool\u003e get isConnected async {\n    final results = await connectivity.checkConnectivity();\n    // Check if there are any connectivity results that aren't 'none'\n    return results.any((result) =\u003e result != ConnectivityResult.none);\n  }\n}\n```\n\n## Repo Implementation\nInitial Repo Implementation is created\n```dart\nclass NewsRepoImpl implements NewsRepo {\n  @override\n  Future\u003cEither\u003cFailure, NewsEntity\u003e\u003e getCountryNews(String country, String category) {\n    // TODO: implement getCountryNews\n    throw UnimplementedError();\n  }\n\n  @override\n  Future\u003cEither\u003cFailure, NewsEntity\u003e\u003e getQueryNews(String query) {\n    // TODO: implement getQueryNews\n    throw UnimplementedError();\n  }\n}\n```\n\n### Test Setup for Repo Impl\n```dart\n@GenerateMocks([NewsRemoteDatasource])\n@GenerateMocks([NewsLocalDatasource])\n@GenerateMocks([NetworkInfo])\n\nvoid main(){\n  NewsRepoImpl newsRepoImpl;\n  MockNewsRemoteDatasource newsRemoteDatasource;\n  MockNewsLocalDatasource newsLocalDatasource;\n  MockNetworkInfo networkInfo;\n\n  setUp((){\n    newsRemoteDatasource =  MockNewsRemoteDatasource();\n    newsLocalDatasource =  MockNewsLocalDatasource();\n    networkInfo = MockNetworkInfo();\n    newsRepoImpl = NewsRepoImpl(\n      newsRemoteDatasource: newsRemoteDatasource, newsLocalDatasource: newsLocalDatasource, networkInfo: networkInfo\n    );\n  });\n}\n```\nRepo Implementation becomes:\n```dart\nclass NewsRepoImpl implements NewsRepo {\n  final NewsRemoteDatasource newsRemoteDatasource;\n  final NewsLocalDatasource newsLocalDatasource;\n  final NetworkInfo networkInfo;\n\n  NewsRepoImpl({required this.newsRemoteDatasource, required this.newsLocalDatasource, required this.networkInfo});\n\n  @override\n  Future\u003cEither\u003cFailure, NewsEntity\u003e\u003e getCountryNews(String country, String category) {\n    // TODO: implement getCountryNews\n    throw UnimplementedError();\n  }\n\n  @override\n  Future\u003cEither\u003cFailure, NewsEntity\u003e\u003e getQueryNews(String query) {\n    // TODO: implement getQueryNews\n    throw UnimplementedError();\n  }\n}\n```\n\n## Repo Implemetation Test\nSetup\n```dart\n@GenerateMocks([NewsRemoteDatasource])\n@GenerateMocks([NewsLocalDatasource])\n@GenerateMocks([NetworkInfo])\nvoid main() {\n  late NewsRepoImpl newsRepoImpl;\n  late MockNewsRemoteDatasource newsRemoteDatasource;\n  late MockNewsLocalDatasource newsLocalDatasource;\n  late MockNetworkInfo networkInfo;\n\n  setUp(() {\n    newsRemoteDatasource = MockNewsRemoteDatasource();\n    newsLocalDatasource = MockNewsLocalDatasource();\n    networkInfo = MockNetworkInfo();\n    newsRepoImpl = NewsRepoImpl(\n      newsRemoteDatasource: newsRemoteDatasource,\n      newsLocalDatasource: newsLocalDatasource,\n      networkInfo: networkInfo,\n    );\n  });\n }\n}\n\ngroup('Get News', () {\n  final tQuery = 'query';\n    final tCountry = 'country';\n    final tCategory = 'category';\n\n    final tNewsModel1 = [\n      NewsModel(\n        sourceId: null,\n        sourceName: 'Reuters',\n        author: 'Reuters',\n        title:\n            'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n        description: null,\n        url:\n            'https://www.reuters.com/world/asia-pacific/passenger-plane-crashes-kazakhstan-emergencies-ministry-says-2024-12-25/',\n        urlToImage: null,\n        publishedAt: DateTime.parse('2024-12-25T08:19:37Z'),\n        content: null,\n      )\n    ];\n    final List\u003cNewsEntity\u003e newsEntity1 = tNewsModel1;\n\n    final tNewsModel2 = [\n      NewsModel(\n        sourceId: '28734685',\n        sourceName: 'Reuters',\n        author: 'Reuters',\n        title:\n            'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n        description: 'Passenger plane crashed',\n        url:\n            'https://www.reuters.com/world/asia-pacific/passenger-plane-crashes-kazakhstan-emergencies-ministry-says-2024-12-25/',\n        urlToImage: 'http://example.com',\n        publishedAt: DateTime.parse('2024-12-25T08:19:37Z'),\n        content:\n            'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n      )\n    ];\n    final List\u003cNewsEntity\u003e newsEntity2 = tNewsModel2;\n}\n```\nTest cases are:\n* should check if the device is online for query news\n* should check if the device is online for country wise news\n\n```dart\ntest('should check if the device is online for query news', () async {\n      // arrange\n      when(networkInfo.isConnected).thenAnswer((_) async =\u003e true);\n      // act\n      newsRepoImpl.getQueryNews(tQuery);\n      // assert\n      verify(networkInfo.isConnected);\n    });\n\n    test('should check if the device is online for country wise news',\n        () async {\n      // arrange\n      when(networkInfo.isConnected).thenAnswer((_) async =\u003e true);\n      // act\n      newsRepoImpl.getCountryNews(tCountry, tCategory);\n      // assert\n      verify(networkInfo.isConnected);\n    });\n```\n### When Device is Online:\nTest cases:\n* Should return remote data when the call is Success for nullable Response\n* Should return remote data when the call is Success for non nullable Response\n* Should return local data when the call is Success for nullable Response\n* Should return local data when the call is Success for non nullable Response\n* Should return server failure when the call is Failure\nFor both QueryNews and CountryNews\n```dart\ntest(\n          'Should return remote data when the call is Success for nullable Response',\n          () async {\n            // arrange\n            when(newsRemoteDatasource.getQueryNews(any))\n                .thenAnswer((_) async =\u003e tNewsModel1);\n\n            // act\n            final result = await newsRepoImpl.getQueryNews(tQuery);\n\n            // assert\n            verify(newsRemoteDatasource.getQueryNews(tQuery));\n            expect(result,\n                Right(newsEntity1)); // Expect a List of NewsEntity directly\n          },\n        );\n\n        test(\n            'Should return remote data when the call is Success for non nullable Response',\n            () async {\n          // arrange\n          when(newsRemoteDatasource.getQueryNews(any))\n              .thenAnswer((_) async =\u003e tNewsModel2);\n\n          // act\n          final result = await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsRemoteDatasource.getQueryNews(tQuery));\n          expect(result, Right(newsEntity2));\n        });\n\n        test(\n            'Should return local data when the call is Success for nullable Response',\n            () async {\n          // arrange\n          when(newsRemoteDatasource.getQueryNews(any))\n              .thenAnswer((_) async =\u003e tNewsModel1);\n\n          // act\n          await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsRemoteDatasource.getQueryNews(tQuery));\n          verify(newsLocalDatasource.cacheNews(tNewsModel1));\n        });\n\n        test(\n            'Should return local data when the call is Success for non nullable Response',\n            () async {\n          // arrange\n          when(newsRemoteDatasource.getQueryNews(any))\n              .thenAnswer((_) async =\u003e tNewsModel2);\n\n          // act\n          await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsRemoteDatasource.getQueryNews(tQuery));\n          verify(newsLocalDatasource.cacheNews(tNewsModel2));\n        });\n\n        test('Should return server failure when the call is Failure', () async {\n          // arrange\n          when(newsRemoteDatasource.getQueryNews(any))\n              .thenThrow(ServerException('Error'));\n\n          // act\n          final result = await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsRemoteDatasource.getQueryNews(tQuery));\n          verifyZeroInteractions(newsLocalDatasource);\n          expect(result, Left(ServerFailure('Error')));\n        });\n      });\n```\n### When Device is Offline:\nTest cases:\n* Should return local data when the call is Failure for nullable Response\n* Should return local data when the call is Failure for non nullable Response\n* Should return cache failure when the local data is Failure\nFor both QueryNews and CountryNews\n```dart\ntest(\n            'Should return local data when the call is Failure for nullable Response',\n            () async {\n          // arrange\n          when(newsLocalDatasource.getLastNews())\n              .thenAnswer((_) async =\u003e tNewsModel1);\n\n          // act\n          final result = await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsLocalDatasource.getLastNews());\n          expect(result, Right(newsEntity1));\n        });\n\n        test(\n            'Should return local data when the call is Failure for non nullable Response',\n            () async {\n          // arrange\n          when(newsLocalDatasource.getLastNews())\n              .thenAnswer((_) async =\u003e tNewsModel2);\n\n          // act\n          final result = await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsLocalDatasource.getLastNews());\n          expect(result, Right(newsEntity2));\n        });\n\n        test('Should return cache failure when the local data is Failure',\n            () async {\n          // arrange\n          when(newsLocalDatasource.getLastNews())\n              .thenThrow(CacheException('Error'));\n\n          // act\n          final result = await newsRepoImpl.getQueryNews(tQuery);\n\n          // assert\n          verify(newsLocalDatasource.getLastNews());\n          expect(result, Left(CacheFailure('Error')));\n        });\n      });\n```\n\nRepo Implementation will be\n```dart\nclass NewsRepoImpl implements NewsRepo {\n  final NewsRemoteDatasource newsRemoteDatasource;\n  final NewsLocalDatasource newsLocalDatasource;\n  final NetworkInfo networkInfo;\n\n  NewsRepoImpl({\n    required this.newsRemoteDatasource,\n    required this.newsLocalDatasource,\n    required this.networkInfo,\n  });\n\n  @override\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e getCountryNews(String country, String category) async {\n    if (await networkInfo.isConnected) {\n      try {\n        final remoteNews = await newsRemoteDatasource.getCountryNews(country, category);\n        newsLocalDatasource.cacheNews(remoteNews);\n        return Right(remoteNews);\n      } catch (e) {\n        return Left(ServerFailure('Error'));\n      }\n    } else {\n      try {\n        final localNews = await newsLocalDatasource.getLastNews();\n        return Right(localNews); // return cached list of news\n      } catch (e) {\n        return Left(CacheFailure('Error'));\n      }\n    }\n  }\n\n  @override\n  Future\u003cEither\u003cFailure, List\u003cNewsEntity\u003e\u003e\u003e getQueryNews(String query) async {\n    if (await networkInfo.isConnected) {\n      try {\n        final remoteNews = await newsRemoteDatasource.getQueryNews(query);\n        newsLocalDatasource.cacheNews(remoteNews);\n        return Right(remoteNews);\n      } catch (e) {\n        return Left(ServerFailure('Error'));\n      }\n    } else {\n      try {\n        final localNews = await newsLocalDatasource.getLastNews();\n        return Right(localNews); // return cached list of news\n      } catch (e) {\n        return Left(CacheFailure('Error'));\n      }\n    }\n  }\n}\n```\n\n## DataSource Test\n### Remote DataSource\nTest Setup\n```dart\n@GenerateMocks([http.Client])\nvoid main() {\n  late NewsRemoteDatasourceImpl datasource;\n  late MockClient mockClient;\n\n  setUp(() {\n    mockClient = MockClient();\n    datasource = NewsRemoteDatasourceImpl(client: mockClient);\n  });\n\n  group('Get Query News', () {\n  final tQuery = 'query';\n  final tNewsModelList = [\n    NewsModel(\n      sourceId: '28734685',\n      sourceName: 'Reuters',\n      author: 'Reuters',\n      title:\n          'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n      description: 'Passenger plane crashed',\n      url:\n          'https://www.reuters.com/world/asia-pacific/passenger-plane-crashes-kazakhstan-emergencies-ministry-says-2024-12-25/',\n      urlToImage: 'http://example.com',\n      publishedAt: DateTime.parse('2024-12-25T08:19:37Z'),\n      content:\n          'Passenger plane flying from Azerbaijan to Russia crashes in Kazakhstan with many feared dead - Reuters',\n    ),\n  ];\n }\n}\n```\n\nTest cases are:\n* should perform GET request on a URL with query\n* should return a list of News Models when status code is 200\n* should throw ServerException when status code is not 200\nfor both getQueryNews and getCountryNews\n```dart\ntest('should perform GET request on a URL with query', () async {\n    // arrange\n    when(mockClient.get(any, headers: anyNamed('headers')))\n        .thenAnswer((_) async =\u003e http.Response(fixture('news_non_null.json'), 200));\n\n    // act\n    await datasource.getQueryNews(tQuery);\n\n    // assert\n    verify(mockClient.get(\n      Uri.parse(\n        \"https://newsapi.org/v2/everything?q=$tQuery\u0026sortBy=publishedAt\u0026apiKey=API_KEY\",\n      ),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    ));\n  });\n\n  test('should return a list of News Models when status code is 200', () async {\n    // arrange\n    when(mockClient.get(any, headers: anyNamed('headers')))\n        .thenAnswer((_) async =\u003e http.Response(fixture('news_non_null.json'), 200));\n\n    // act\n    final result = await datasource.getQueryNews(tQuery);\n\n    // assert\n    expect(result, equals(tNewsModelList));\n  });\n\n  test('should throw ServerException when status code is not 200', () async {\n    // arrange\n    when(mockClient.get(any, headers: anyNamed('headers')))\n        .thenAnswer((_) async =\u003e http.Response('Something went wrong', 404));\n\n    // act\n    final call = datasource.getQueryNews;\n\n    // assert\n    expect(\n      () =\u003e call(tQuery),\n      throwsA(isA\u003cServerException\u003e().having((e) =\u003e e.message, 'message', 'Error')),\n    );\n  });\n```\nSimiliarly for getCountryNews\nThe Remote DataSource Implementation will be\n```dart\nclass NewsRemoteDatasourceImpl implements NewsRemoteDatasource {\n  final http.Client client;\n\n  NewsRemoteDatasourceImpl({required this.client});\n\n  @override\n  Future\u003cList\u003cNewsModel\u003e\u003e getCountryNews(\n      String country, String category) async {\n    final response = await client.get(\n      Uri.parse(\n        \"https://newsapi.org/v2/top-headlines?country=$country\u0026category=$category\u0026apiKey=API_KEY\",\n      ),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    );\n\n    if (response.statusCode == 200) {\n      final responseBody = json.decode(response.body) as Map\u003cString, dynamic\u003e;\n      final articles = (responseBody['articles'] as List)\n          .map((article) =\u003e NewsModel.fromJson(article))\n          .toList();\n      return articles;\n    } else {\n      throw ServerException('Error');\n    }\n  }\n\n  @override\n  Future\u003cList\u003cNewsModel\u003e\u003e getQueryNews(String query) async {\n    final response = await client.get(\n      Uri.parse(\n        \"https://newsapi.org/v2/everything?q=$query\u0026sortBy=publishedAt\u0026apiKey=API_KEY\",\n      ),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    );\n    print(response.body);\n    if (response.statusCode == 200) {\n      final responseBody = json.decode(response.body) as Map\u003cString, dynamic\u003e;\n      final articles = (responseBody['articles'] as List)\n          .map((article) =\u003e NewsModel.fromJson(article))\n          .toList();\n      return articles;\n    } else {\n      throw ServerException('Error');\n    }\n  }\n}\n```\n\n## Local Datasource\nTest Setup\n```dart\n@GenerateMocks([SharedPreferences])\nvoid main() {\n  late NewsLocalDatasourceImpl datasource;\n  late MockSharedPreferences mockSharedPreferences;\n\n  setUp(() {\n    mockSharedPreferences = MockSharedPreferences();\n    datasource = NewsLocalDatasourceImpl(sharedPreferences: mockSharedPreferences);\n  });\n }\n}\n```\n\nTest cases are:\n* should return News from shared preferences if present\n* should throw CacheFailure when there is no cache value\n* should call shared preferences to Cache the data\nfor both getQueryNews and getCountryNews\n\n```dart\ntest('should return News from shared preferences if present', () async {\n      // Arrange\n      const tKey = 'CACHED_NEWS'; \n      final tNewsJson = fixture('news_cached.json'); // Assuming this fixture is a valid JSON string\n      when(mockSharedPreferences.getString(tKey)).thenReturn(tNewsJson);\n\n      // Act\n      final result = await datasource.getLastNews();\n\n      // Assert\n      verify(mockSharedPreferences.getString(tKey));  // Verify the correct method is called\n      expect(result, equals(tNewsModelList));  // Compare with the list of models parsed from the fixture\n    });\n\n    test('should throw CacheFailure when there is no cache value', () async {\n      // Arrange\n      const tKey = 'CACHED_NEWS';\n      when(mockSharedPreferences.getString(tKey)).thenReturn(null);\n\n      // Act \u0026 Assert\n      expect(\n        () =\u003e datasource.getLastNews(),\n        throwsA(isA\u003cCacheFailure\u003e()),\n      );\n    });\n```\n```dart\n    test('should call shared preferences to Cache the data', () async {\n      // Act\n      await datasource.cacheNews(tNewsModelList); // Cache the news\n\n      // Arrange\n      final jsonString =\n          json.encode(tNewsModelList.map((model) =\u003e model.toJson()).toList()); // Serialize list of models to JSON\n\n      // Assert\n      verify(mockSharedPreferences.setString('CACHED_NEWS', jsonString)); // Verify that setString was called with the expected arguments\n    });\n```\nNow the Local DataSource Implementation will be:\n```dart\nclass NewsLocalDatasourceImpl implements NewsLocalDatasource {\n  final SharedPreferences sharedPreferences;\n\n  static const CACHED_NEWS_KEY = 'CACHED_NEWS';\n\n  NewsLocalDatasourceImpl({required this.sharedPreferences});\n\n  @override\n  Future\u003cvoid\u003e cacheNews(List\u003cNewsModel\u003e newsToCache) async {\n    // Convert the list of NewsModel to a JSON-encoded string\n    final List\u003cMap\u003cString, dynamic\u003e\u003e jsonList =\n        newsToCache.map((news) =\u003e news.toJson()).toList();\n    await sharedPreferences.setString(\n      CACHED_NEWS_KEY,\n      json.encode(jsonList),\n    );\n  }\n\n  @override\n  Future\u003cList\u003cNewsModel\u003e\u003e getLastNews() {\n    final jsonString = sharedPreferences.getString(CACHED_NEWS_KEY);\n    if (jsonString != null) {\n      try {\n        // Decode JSON and map it to a List\u003cNewsModel\u003e\n        final List\u003cdynamic\u003e jsonList = json.decode(jsonString) as List\u003cdynamic\u003e;\n        final List\u003cNewsModel\u003e newsList = jsonList\n            .map((jsonItem) =\u003e NewsModel.fromJson(jsonItem as Map\u003cString, dynamic\u003e))\n            .toList();\n        return Future.value(newsList);\n      } catch (e) {\n        throw CacheFailure('Error parsing cached news');\n      }\n    } else {\n      throw CacheFailure('No cached news found');\n    }\n  }\n}\n```\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasif-faizal%2Finformed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasif-faizal%2Finformed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasif-faizal%2Finformed/lists"}