Giter Club home page Giter Club logo

http-mock-adapter's People

Contributors

alexgorgadz3 avatar cyberail avatar dricholm avatar eltehupkes avatar giorgiberiashvili avatar hannnes1 avatar humblerookie avatar jindrazak avatar kiruel avatar kuhnroyal avatar lukagiorgadze avatar lunakoan avatar maple135790 avatar nataliehippe avatar ncb000gt avatar orevial avatar sebastianbuechler avatar sebastianklaiber avatar theiskaa avatar wangdengwu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

http-mock-adapter's Issues

Support multiple platforms (pub.dev)

Description

The score of the package is not maximum due to an issue regarding multi-platform support.

The issue is described as:

Package not compatible with runtime js
Because:
  • package:http_mock_adapter/http_mock_adapter.dart that imports:
  • package:http_mock_adapter/src/adapters/dio_adapter.dart that imports:
  • package:dio/adapter.dart that imports:
  • package:dio/src/adapters/io_adapter.dart that imports:
  • dart:io

The traced information depicts that Dio uses dart:io package in its source code, which is not compatible with JavaScript runtime (Meaning that Dio is not completely compatible for web usage).

The problem may be solved by conditional importing, universal_io or other method.

The issue in the context of http-mock-adapter will be pertinent until Dio fixes the issue.

Tip: Use pana to calculate package's score locally.

Migrate to null safety

Description

Dart has recently obtained the ability to enable sound null safety in Dart code. Here is the overview of the feature. It is currently in beta.

From the migration guide we see the following notice:

Migrating an app is technically the same as migrating a package. Before migrating an app, consider waiting until null safety is in a stable release and all your dependencies are ready.

This means that it's generally recommended to migrate if developing a Dart package. Thus, it would seem beneficial to implement null safety features in http_mock_adapter.

Throw DioError to test error handling

Is your feature request related to a problem? Please describe.
How to throw exceptions from Dio to test error handling?

Describe the solution you'd like
I think pretty useful for doing something like

dioAdapter.onGet('/test').throw(DioError(type: DioErrorType.RESPONSE, response: Response(statusCode: 500)));

thanks for the package

Mocking Post with FormData

Description

I've been unable to mock a Post that sends FormData. Try as I might, I can't setup the route correctly:

Assertion failed: "Could not find mocked route matching request for https://example.com/POST/Instance of 'FormData'/{}/{content-type: multipart/form-data; boundary=--dio-boundary-4160721626, content-length: 173}"

Simplified, the method I'm trying to test looks like something this:

  Future<void> postSomething(
      {required String url, required FormData formData}) async {
    try {
      final response = await _httpClient.post(url, data: formData);
      if (response.statusCode != 200) {
        print('Request failed ${response.statusCode}');
      }
    } on DioError catch (e) {
      print(e.message);
      print(e.type);

      if (e.type == DioErrorType.connectTimeout ||
          e.type == DioErrorType.receiveTimeout) {
        print('Doing something cool with a timeout error');
        throw Exception('TIMEOUT');
      } else {
        print('Doing something cool with unexepcted error');
      }
    }
  }

The current iteration of my test looks like:

    test('postSomething', () {
      final errorToThrow = DioError(
          requestOptions: RequestOptions(path: path),
          type: DioErrorType.receiveTimeout,
          response: Response(
            statusCode: 408,
            requestOptions: RequestOptions(path: path),
          ),
          error: {'message': 'Timeout'});

      print('Form Length: ${formData.length}');
      print('Form Boundary: ${formData.boundary}');

      dioAdapter.onPost(path, (request) => request.throws(408, errorToThrow),
          data: formData);

      final postService = PostService(httpClient: dio);

      expect(
        () async =>
            await postService.postSomething(url: path, formData: formData),
        throwsA(
          predicate(
            (e) => e is Exception && e.toString().startsWith('TIMEOUT'),
          ),
        ),
      );
    });

Steps to reproduce

Working example here: https://github.com/ComplexCarbos/dio_mock_example

Expected behavior

Can Mock a Post request that takes FormData as a parameter.

analysis_options.yml static analysis configuration

Description

analysis_options is Dart's official static analysis tool.

There are numerous well-defined and pre-configured analysis options available, such as pedantic, effective_dart, and maybe on the stricter side, - http.

It is critical for a package that is intended to be used as a library to be less prone to fail under both defined and undefined circumstances, therefore I advise to implement numerous strict static analysis rules.

DioError [DioErrorType.other]: Converting object to an encodable object failed: Instance of 'Genre'

I have a test written like this:

void main() {
  test("Function should return a Genre", () async {
    final Dio dio = Dio();
    final DioAdapter dioAdapter = DioAdapter();

    dio.httpClientAdapter = dioAdapter;

    dioAdapter.onGet('This is a test endpoint',
        (request) => request.reply(200, Genre('Romance', '/romance', 100, 50)));

    ApiClientProvider apiClientProvider = ApiClientProvider(dio);

    expect(
        await apiClientProvider.getGenreInfo(
            endpoint: 'This is a test endpoint'),
        isInstanceOf<Genre>());
  });
}

This is the getGenreInfo() method from ApiClientProvider class:

  Future<Genre> getGenreInfo({required String endpoint}) async {
    Response response = await _dio.get(endpoint);
    return response.data;
  }

I tried to mock the get() function to return the Genre('Romance', '/romance', 100, 50) but I end up getting DioError [DioErrorType.other]: Converting object to an encodable object failed: Instance of 'Genre'

Response not being mocked when pushing a new screen

Description

When doing widget testing, if we are testing Screen 1 and need to mock a response that happens in Screen 2, mock won't be available when Screen 2 is pushed and will get Pending Timer error.
After debugging, I see that the client is trying to make the actual API call because there are no mocked responses.

In the same test, if I mock an API call that happens in that screen, mocking works fine.

Steps to reproduce

  1. Create 2 screens where Screen 1 pushes Screen 2, screen 2 having to perform an API call.
  2. Crate a test where Screen 1 pushes Screen 2 and mock response for Screen 2 API call.

Expected behavior

Response is mocked properly.

System details

Flutter 2.10.4

Additional context

We are using AutoRouter for navigation, but I don't think this affects anything.

Mocking dio : Could not find mocked route matching

I am trying to make unit test with mockitto to test dio.

void main() {
  group("Splash init", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    const path = 'https://..../mobile/';

    setUpAll(() {
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;
    });

test(
    "call getMainConfigs -> getMainConfig web service called 404 exception",
    () async {
  final dioError = DioError(
    error: {'message': 'Some beautiful error!'},
    request: RequestOptions(path: path),
    response: Response(
      statusCode: 500,
      request: RequestOptions(path: path),
    ),
    type: DioErrorType.RESPONSE,
  );

  dioInterceptor.onPost(path, (request) => request.throws(500, dioError),
      headers: {'Content-Type': 'application/json; charset=utf-8'});
  expect(() async => await dio.post(path), throwsA(isA<AdapterError>()));
  // expect(() async => await dio.get(path), throwsA(isA<DioError>()));
  // expect(
  //   () async => await dio.get(path),
  //   throwsA(
  //     predicate(
  //       (DioError error) =>
  //           error is DioError &&
  //           error is AdapterError &&
  //           error.message == dioError.error.toString(),
  //     ),
  //   ),
  // );
});
  });
}

but after running this test I got this error:

Expected: throws <Instance of 'AdapterError'>
  Actual: <Closure: () => Future<Response<dynamic>>>
   Which: threw DioError:<DioError [DioErrorType.DEFAULT]: Assertion failed: "Could not find mocked route matching request for https://..../mobile//POST/null/{}/{content-type: application/json; charset=utf-8}"
#0      History.responseBody.<anonymous closure>
package:http_mock_adapter/src/history.dart:32
#1      DioInterceptor.onRequest
package:http_mock_adapter/…/interceptors/dio_interceptor.dart:63
#2      DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:849
#3      DioMixin.checkIfNeedEnqueue
package:dio/src/dio.dart:1121
#4      DioMixin._request._interceptorWrapper.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:846
#5      new Future.<anonymous closure> (dart:async/future.dart:174:37)
#6      StackZoneSpecification._run
package:stack_trace/src/stack_zone_specification.dart:208
#7      StackZoneSpecification._registerCallback.<anonymous closure>
package:stack_trace/src/stack_zone_specification.dart:116

I am trying to test exception.

Incompatible with retrofit

Description

I am using retrofit package. When I run tests, the following error keeps showing:
Could not find mocked route matching request for /api/users/GET/{}/{page: 1}/{}
When I debug, I see the client sends perfectly normal requests, however, for some reason the adapter cannot resolve it properly.

Steps to reproduce

  1. Install retrofit: ^2.0.0-beta1
  2. Setup an API client and some endpoints
  3. Use DioAdapter() as the httpAdapter of the Dio client.

System details

The code is null-safe.

Github Action for publishing on pub.dev

Description

Github action should publish package after adding release tag to the main branch

Steps to reproduce

  1. Add tag to develop branch
  2. Create PR main <- develop

Expected behavior

Github action (publish package to pub.dev) should start.

Screenshots

Nothing happens

System details

Github.com

Mocking non-JSON response

Description

For my current use case I need to mock a response for an application/xml content response. But since the mocked response body is always passed to jsonEncode() in RequesHandler the body will be surrounded with "s, while existing ones will be escaped to \".

Steps to reproduce

  1. Mock a response body with a String data.
  2. Verify that actual response is equal to mocked data.

A simple test to reproduce:

void main() {
  final dioAdapter = DioAdapter();
  final dio = Dio()..httpClientAdapter = dioAdapter;

  test('should not encode body with jsonEncode', () async {
    const expected = '<xml xmlns="http://www.w3.org/2005/Atom">Test</xml>';
    dioAdapter.onGet(
      '/',
      (request) => request.reply(
        200,
        expected,
        headers: {
          Headers.contentTypeHeader: ['application/xml'],
        },
      ),
    );

    final response = await dio.get<String>(
      '/',
      options: Options(
        responseType: ResponseType.plain,
      ),
    );

    expect(response.data, expected);
  });
}

Expected behavior

Response body is not encoded, remains as plain string.

Screenshots

Screenshot of the above test result.
image

System details

OS: Windows 10
Flutter 2.2.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision b22742018b (12 days ago) • 2021-05-14 19:12:57 -0700
Engine • revision a9d88a4d18
Tools • Dart 2.13.0

Additional context

The fix should probably be to conditionally apply jsonEncode() depending on the header type given. Current implementation has a default header that contains Headers.jsonContentType.
Maybe something similar to this in request_handler.dart?

final isJson =
      headers[Headers.contentTypeHeader]?.contains(Headers.jsonContentType) ??
          false;

  requestMap[this.statusCode] = () => AdapterResponse.from(
        isJson ? jsonEncode(data) : data,
        this.statusCode,
        headers: headers,
        statusMessage: statusMessage,
        isRedirect: isRedirect,
      );

This will make the above test pass.

I also noticed the request_handler.dart doesn't have any tests. so if something like my proposal would be okay I could also open a PR for it. Please let me know what you think/any changes could be applied.

EditorConfig support

Description

EditorConfig is a working environment agnostic intended to maintain consistent coding style while working on the project.

It would be useful to implement Dart/Flutter friendly EditorConfig implementation, such as:

# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false

The getMockFileName utility function is bugged and needs testing

Description

getMockFileName() located in utils.dart file is currently bugged.

Its functionality lies in modifying uriPath strings from slashed versions to dashed versions, such as: /accounts => -accounts

This is due to the reason that filePath string is formatted like so: ${options.method}$mockFileName.json

It fails when we pass in something such as https://example.com due to its implementation details.

It would be very beneficial to test numerous different uriPath cases on this function to assess its effectiveness.

clear adapter call history

Provide a way to clear previous call from adapter history to be called on every tearDown during tests.

Chaining onGet expectations

Description

I'm trying to provide a chain of results for a given path, so the first request would fail with a 401, and the second request would succeed. Currently, the Dio interceptor is being called repeatedly with the same response. With the expectations below, 401 is repeatedly received

Steps to reproduce

the test expectations:

_dioAdapter
    ..onGet(
      '/Test',
      (request) => request.throws(401, error(401)),
      headers: {
        'accept-language': 'en',
        'accept': 'application/json',
        'authorization': 'bearer ${accessToken.token}',
      },
    )
    ..onGet(
      '/Test',
      (request) => request.reply(200, ''),
      headers: {
        'accept-language': 'en',
        'accept': 'application/json',
        'authorization': 'bearer ${refreshedAccessToken.token}',
      },
    );

Expected behavior

I would expect subsequent calls to the path to use the responses in the order given:

The first response would be the 401, followed by the 200.

Additional context

The code being tested is for refreshing auth tokens.

http_mock_adapter: ^0.2.1
dio: ^4.0.0

Implement GitHub Actions workflow for automated building, formatting, analyzing, testing

Description

GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.

It would be beneficial to automate the testing workflow while making sure that no broken code gets merged into releases, alongside other benefits of Continuous Integration.

Implement Code of Conduct

Description

A Code of Conduct is a set of rules outlining the norms, rules, and responsibilities or proper practices of an individual party or an organization.

Add queryParameters to option to adapter RequestHandler factory methods

Description

There doesn't seem to be an option to match against query parameters while using request handlers.
I tried adding the param to the URL string,

dioAdapter.onGet('/search?param=$query')

However, this doesn't seem to match requests correctly.
Using the onRoute API does work for this use case but it's a lot more verbose.

dioAdapter
  .onRoute(
    '/search',
    request: Request(
      route: '/search',
      method: RequestMethods.GET,
      headers: {'authorization': 'Bearer $token'},
      queryParameters: {
        'param': 'query',
      },
    ),
  );

It would be nice to expose an API for this on the factory methods.

Allow to ignore headers by name when trying to match a request

Description

When changing POST request content in a test I always have to update the content-length header to match the new length.
To make this easier, I would like to ignore some headers when requests are matched.

This could be solved with a complex request matching implementation (like #68) which I would really love to see.

Option Headers cause Assertion error on POST/PUT/DELETE

Supplying headers to dioAdapter.onGet works as expected. But does not work for POST, PUT and DELETE (what I tested).

Matching headers/options along with a matching path result in an Assertion error: "Could not find mocked route matching request for..."

Code to reproduce:

import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';

void main() async {
  late Dio dio;
  late DioAdapter dioAdapter;
  const path = 'https://example.com';
  final headers = <String, String>{'Authorization': 'Bearer authToken'};

  setUp(() {
    dio = Dio();
    dioAdapter = DioAdapter();
    dio.httpClientAdapter = dioAdapter;

    dioAdapter
      ..onGet(
        path,
        (request) =>
            request.reply(200, {'message': 'Successfully mocked GET!'}),
        headers: headers,
      )
      ..onPost(
        path,
        (request) =>
            request.reply(200, {'message': 'Successfully mocked POST!'}),
        headers: headers, // NOT WORKING
      )
      ..onDelete(
        path,
        (request) =>
            request.reply(200, {'message': 'Successfully mocked DELETE!'}),
        headers: headers, // NOT WORKING
      )
      ..onPut(
        path,
        (request) =>
            request.reply(200, {'message': 'Successfully mocked PUT!'}),
        headers: headers, // NOT WORKING
      );
  });

  // WORKING
  test('on get with headers', () async {
    final onGetResponse = await dio.get<dynamic>(
      path,
      options: Options(
        headers: headers,
      ),
    );
    print(onGetResponse.data);
  });

  // NOT WORKING
  test('on post with headers', () async {
    final onPostResponse = await dio.post<dynamic>(
      path,
      options: Options(
        headers: headers,
      ),
    );
    print(onPostResponse.data);
  });

  // NOT WORKING
  test('on delete with headers', () async {
    final onPostResponse = await dio.post<dynamic>(
      path,
      options: Options(
        headers: headers,
      ),
    );
    print(onPostResponse.data);
  });

  // NOT WORKING
  test('on put with headers', () async {
    final onPostResponse = await dio.put<dynamic>(
      path,
      options: Options(
        headers: headers,
      ),
    );
    print(onPostResponse.data);
  });
}

Example error for onPost test:

DioError [DioErrorType.other]: Assertion failed: "Could not find mocked route matching request for https://example.com/POST/null/{}/{Authorization: Bearer authToken, content-type: application/json; charset=utf-8}"
#0      History.responseBody.<anonymous closure>
package:http_mock_adapter/src/history.dart:32
#1      DioAdapter.fetch
package:http_mock_adapter/…/adapters/dio_adapter.dart:47
#2      DioMixin._dispatchRequest
package:dio/src/dio_mixin.dart:632
<asynchronous suspension>
#3      StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
<asynchronous suspension>
2

DioMixin.fetch.<fn>
package:dio/src/dio_mixin.dart:618
===== asynchronous gap ===========================
dart:async                                                      Future.catchError
DioMixin.fetch
package:dio/src/dio_mixin.dart:609
DioMixin.request
package:dio/src/dio_mixin.dart:466
DioMixin.post
package:dio/src/dio_mixin.dart:89
main.<fn>
test/…/api/dio_test.dart:56
main.<fn>
test/…/api/dio_test.dart:55
2
✖ on post with headers

Versions:
http_mock_adapter : 0.2.1
did: 4.0.0

Thanks for the package and help!

`AdapterInterface` causes code duplication problems.

Problem begins from the, RequestHandler's reply function:
RequestHandler's reply method was returning only DioAdapter which caused chaining problems for DioInterceptor, because DioInterceptor itself needs DioInterceptor to be returned from reply to chain methods and track the History.

So we needed to distinguish returns inside the reply function, this problem was going to be solved with creating generic type parameter for RequestHandler class, but while doing this another problem occurred: reply function was not able to understand what type it was returning(DioAdapter or DioInterceptor).

For the above mentioned problem two ways of the solution were suggested to solve it.

First, define an abstract interface AdapterInterface for both, DioInterceptor and DioAdapter, also we need to define all the methods(onGet , onPost......) inside the interface and those methods will be overriden from mixin RequestRouted.
In this way reply function return type is AdapterInterface and also reply function maintains it's code completion ability when you are creating chains, But there is big problem of code management because interface defines all of the methods from ReqeustRouted with their parameters , if we have to change parameters' types or quantities of all of the methods(onGet,onPost......) inside the RequestRouted mixin we will have to change everything inside interface too.

Second, reply functions return type will be T and it will return DioAdapter or DioInterceptor with following manner: return DioAdapter() as T or return DioInterceptor() as T, In this case there is no need for separate interface but this type of solution destroyed auto completion ability for reply() method while creating chains.

For now, wile prioritizing auto completion, First method was left for use, But we are looking for other ways of solution and open to suggestions.

Always matches when one route was matched

Hi! Those tests should show what's happening. The last test fails and it (IMO) shouldn't:

Versions

  • http_mock_adapter - 0.2.1
  • dio - 4.0.0
Flutter 2.0.5 • channel unknown • unknown source
Framework • revision adc687823a (11 days ago) • 2021-04-16 09:40:20 -0700
Engine • revision b09f014e96
Tools • Dart 2.12.3

image

import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';

void main() {
  late Dio dio;
  late DioAdapter adapter;
  setUp(() {
    dio = Dio();
    adapter = DioAdapter();
    dio.httpClientAdapter = adapter;

    adapter.onGet('/test', (request) {
      request.reply(200, 'ok');
    });
  });

  test('correctly throws when does not match first request', () async {
    expectLater(
      dio.get<void>('/wont-find'),
      throwsA(isA<DioError>()),
    );
  });

  test('correctly not throws when matches first request', () async {
    expectLater(
      dio.get<void>('/test'),
      completes,
    );
  });

  test(
      'INCORRECTLY not throws when matches '
      'first request but not the next ones', () async {
    expectLater(
      dio.get<void>('/test'),
      completes,
    );

    expectLater(
      dio.get<void>('/wont-find'),
      throwsA(isA<DioError>()),
    );
  });
}

Create ability for delayed response:

Create ability for delayed response:

Current vision for interface:

dio..onGet("path", 
     (request){}, 
    delay: Duration(seconds:1),
)

In DioAdapter or DiopInterceptor delay param will be checked for null if it is null adapter will respond normally, if not it will respond with specified delay

Add and export DioAdapterMockito

Description

One of the implementers of the core package functionality is ought to be DioAdapterMockito.

Implementation ideas are welcome!

NoSuchMethodError: The getter 'headers' was called on null

I am trying to mocking Dio . So this is my test to test exception according your example:

void main() {
  group("Splash init", () {
    DioAdapterMockito dioAdapterMockito;
    Dio dio;
    const path = 'https://example/mobileFlutter/1.0/';

    setUpAll(() {
      registerServices();
      dioAdapterMockito = DioAdapterMockito();
      dio = Dio()..httpClientAdapter = dioAdapterMockito;
    });

    tearDownAll(() {
      print("Called teardown");
    });

    test(
        "call getMainConfigs -> getMainConfig web service called 404 exception",
        () async {
      final dioError = DioError(
        error: {'message': 'Some beautiful error!'},
        request: RequestOptions(path: path),
        response: Response(
          statusCode: 500,
          request: RequestOptions(path: path),
        ),
        type: DioErrorType.RESPONSE,
      );

      dioAdapterMockito.onGet(
        path,
        (request) => request.throws(500, dioError),
      );
      expect(() async => await dio.get(path), throwsA(isA<AdapterError>()));
      expect(() async => await dio.get(path), throwsA(isA<DioError>()));
      expect(
        () async => await dio.get(path),
        throwsA(
          predicate(
            (DioError error) =>
                error is DioError &&
                error is AdapterError &&
                error.message == dioError.error.toString(),
          ),
        ),
      );
    });
  });

But I got this error:

Expected: throws satisfies function
  Actual: <Closure: () => Future<Response<dynamic>>>
   Which: threw DioError:<DioError [DioErrorType.DEFAULT]: NoSuchMethodError: The getter 'headers' was called on null.
                Receiver: null
                Tried calling: headers
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1      DioMixin._dispatchRequest
package:dio/src/dio.dart:927
                <asynchronous suspension>
#2      StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
                <asynchronous suspension>
                >
stack package:dio/src/dio.dart 966:7                     DioMixin._dispatchRequest
package:dio/src/dio.dart:966....

I am using dio: ^3.0.9 and this is doctor:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.5, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] VS Code (version 1.55.2)
[✓] Connected device (1 available)

! Doctor found issues in 1 category.

Code checking and quality testing with Git hooks

Description

Git hooks is a mechanism to automate work whenever certain important events occur, such as commits and pull requests.

It is notable that there are numerous Git hooks managers that make working with Git hooks noticeably easier, such as lefthook.

Example configuration for lefthook (keep in mind that you will have to set up lefthook locally on your system):

pre-push:
  parallel: true
  commands:
    tests:
      run: flutter test

pre-commit:
  commands:
    pretty:
      glob: "*.dart"
      run: flutter format {staged_files} && git add {staged_files}
    linter:
      run: flutter analyze

Mock Response modification & request patterns feature

Sometimes when i PUT PATCH or POST data I need to receive different responses over time, so I guess it will be great to make response builder function instead of direct static response in reply method. This may be trigged on iteration, as instance: I send first request and receive RESP#1 and on the next requests i will Receive RESP#2 only. (Otherwise if i will try to place my own returnable method with proposed logic will it work though?...)

The second feature is to include the path patterns for the route, that will allow to put modifiable data e.g. uuids etc. in the path according to REST API patterns. The further enhancement might also include passing those modifiable path parameters to response builder I proposed above so developers could build their own custom response every time.

Hope my thoughts were useful enough, thank you.

DioInterceptor throwing GET request is not handled

Description

Using the DioInterceptor, should a request throw a DioError, other interceptors are not informed.

dio_interceptor.dart contains the method void onRequest(options, handler) async, and the following is the issue:

if (isError(response)) {
  handler.reject(response as DioError);
  return;
}

reject should be passing callFollowingErrorInterceptor = true

void reject(DioError error, [bool callFollowingErrorInterceptor = false])
package:dio/src/interceptor.dart

Complete the request with an error! Other request/response interceptor(s) will not be executed, but error interceptor(s) may be executed, which depends on whether the value of parameter [callFollowingErrorInterceptor] is true.

[error]: Error info to reject. [callFollowingErrorInterceptor]: Whether to call the error interceptor(s).

Stacktrace:

DioError [DioErrorType.response]: {message: Some beautiful error!}
  dart:async/future_impl.dart 25:44                                      _Completer.completeError
  package:dio/src/interceptor.dart 120:16                                RequestInterceptorHandler.reject
  package:http_mock_adapter/src/interceptors/dio_interceptor.dart 67:15  DioInterceptor.onRequest
  package:dio/src/dio_mixin.dart 502:28                                  DioMixin.fetch._requestInterceptorWrapper.<fn>.<fn>.<fn>
  package:dio/src/dio_mixin.dart 795:22                                  DioMixin.checkIfNeedEnqueue
  package:dio/src/dio_mixin.dart 500:22                                  DioMixin.fetch._requestInterceptorWrapper.<fn>.<fn>
  dart:async/future.dart 174:37                                          new Future.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15           StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48           StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1346:47                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1162:7                                            _CustomZone.runGuarded
  dart:async/zone.dart 1202:23                                           _CustomZone.bindCallbackGuarded.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15           StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 116:48           StackZoneSpecification._registerCallback.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1186:23                                           _CustomZone.bindCallback.<fn>
  dart:async-patch/timer_patch.dart 18:15                                Timer._createTimer.<fn>
  dart:isolate-patch/timer_impl.dart 395:19                              _Timer._runTimers
  dart:isolate-patch/timer_impl.dart 426:5                               _Timer._handleMessage
  dart:isolate-patch/isolate_patch.dart 184:12                           _RawReceivePortImpl._handleMessage
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1279:19                                           _CustomZone.registerCallback
  dart:async/zone.dart 1201:22                                           _CustomZone.bindCallbackGuarded
  dart:async/timer.dart 48:45                                            new Timer
  dart:async/timer.dart 81:9                                             Timer.run
  dart:async/future.dart 172:11                                          new Future
  package:dio/src/dio_mixin.dart 499:13                                  DioMixin.fetch._requestInterceptorWrapper.<fn>
  package:dio/src/dio_mixin.dart 494:14                                  DioMixin.fetch._requestInterceptorWrapper.<fn>
  package:stack_trace/src/stack_zone_specification.dart 126:26           StackZoneSpecification._registerUnaryCallback.<fn>.<fn>
  package:stack_trace/src/stack_zone_specification.dart 208:15           StackZoneSpecification._run
  package:stack_trace/src/stack_zone_specification.dart 126:14           StackZoneSpecification._registerUnaryCallback.<fn>
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1286:19                                           _CustomZone.registerUnaryCallback
  dart:async/future_impl.dart 293:23                                     Future.then
  package:dio/src/dio_mixin.dart 576:23                                  DioMixin.fetch.<fn>
  dart:collection/list.dart 86:13                                        ListMixin.forEach
  package:dio/src/dio_mixin.dart 575:18                                  DioMixin.fetch
  package:dio/src/dio_mixin.dart 466:12                                  DioMixin.request
  package:dio/src/dio_mixin.dart 53:12                                   DioMixin.get
  test\util\dio_service_test.dart 505:39                                 main.<fn>.<fn>.<fn>.<fn>
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1286:19                                           _CustomZone.registerUnaryCallback
  dart:async-patch/async_patch.dart 45:22                                _asyncThenWrapperHelper
  test\util\dio_service_test.dart                                        main.<fn>.<fn>.<fn>.<fn>
  package:test_api/src/backend/declarer.dart 200:19                      Declarer.test.<fn>.<fn>
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1286:19                                           _CustomZone.registerUnaryCallback
  dart:async-patch/async_patch.dart 45:22                                _asyncThenWrapperHelper
  package:test_api/src/backend/declarer.dart                             Declarer.test.<fn>.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1789:10                                           _runZoned
  dart:async/zone.dart 1711:10                                           runZoned
  package:test_api/src/backend/declarer.dart 198:13                      Declarer.test.<fn>
  package:test_api/src/backend/declarer.dart 181:48                      Declarer.test.<fn>
  package:test_api/src/backend/invoker.dart 231:15                       Invoker.waitForOutstandingCallbacks.<fn>
  package:test_api/src/backend/invoker.dart 228:14                       Invoker.waitForOutstandingCallbacks.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1789:10                                           _runZoned
  dart:async/zone.dart 1711:10                                           runZoned
  package:test_api/src/backend/invoker.dart 228:5                        Invoker.waitForOutstandingCallbacks
  package:test_api/src/backend/invoker.dart 383:17                       Invoker._onRun.<fn>.<fn>.<fn>
  ===== asynchronous gap ===========================
  dart:async/zone.dart 1286:19                                           _CustomZone.registerUnaryCallback
  dart:async-patch/async_patch.dart 45:22                                _asyncThenWrapperHelper
  package:test_api/src/backend/invoker.dart                              Invoker._onRun.<fn>.<fn>.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1789:10                                           _runZoned
  dart:async/zone.dart 1711:10                                           runZoned
  package:test_api/src/backend/invoker.dart 370:9                        Invoker._onRun.<fn>.<fn>
  package:test_api/src/backend/invoker.dart 415:15                       Invoker._guardIfGuarded
  package:test_api/src/backend/invoker.dart 369:7                        Invoker._onRun.<fn>
  package:stack_trace/src/chain.dart 94:24                               Chain.capture.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1789:10                                           _runZoned
  dart:async/zone.dart 1711:10                                           runZoned
  package:stack_trace/src/chain.dart 92:12                               Chain.capture
  package:test_api/src/backend/invoker.dart 368:11                       Invoker._onRun
  package:test_api/src/backend/live_test_controller.dart 153:11          LiveTestController.run
  package:test_api/src/remote_listener.dart 255:16                       RemoteListener._runLiveTest.<fn>
  dart:async/zone.dart 1354:13                                           _rootRun
  dart:async/zone.dart 1258:19                                           _CustomZone.run
  dart:async/zone.dart 1789:10                                           _runZoned
  dart:async/zone.dart 1711:10                                           runZoned
  package:test_api/src/remote_listener.dart 254:5                        RemoteListener._runLiveTest
  package:test_api/src/remote_listener.dart 207:7                        RemoteListener._serializeTest.<fn>
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:async/zone.dart 1370:13                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:async/stream_controller.dart 826:13                               _StreamSinkWrapper.add
  package:stream_channel/src/guarantee_channel.dart 125:12               _GuaranteeSink.add
  package:stream_channel/src/multi_channel.dart 159:31                   new _MultiChannel.<fn>
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:_internal/async_cast.dart 85:11                                   CastStreamSubscription._onData
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:async/stream_controller.dart 826:13                               _StreamSinkWrapper.add
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_pipe.dart 123:11                                     _ForwardingStreamSubscription._add
  dart:async/stream_pipe.dart 218:10                                     _MapStream._handleData
  dart:async/stream_pipe.dart 153:13                                     _ForwardingStreamSubscription._handleData
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:_http/websocket_impl.dart 1145:21                                 new _WebSocketImpl._fromSocket.<fn>
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_transformers.dart 63:11                              _SinkTransformerStreamSubscription._add
  dart:async/stream_transformers.dart 13:11                              _EventSinkWrapper.add
  dart:_http/websocket_impl.dart 338:23                                  _WebSocketProtocolTransformer._messageFrameEnd
  dart:_http/websocket_impl.dart 232:46                                  _WebSocketProtocolTransformer.add
  dart:async/stream_transformers.dart 111:24                             _SinkTransformerStreamSubscription._handleData
  dart:async/zone.dart 1362:47                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:io-patch/socket_patch.dart 2160:41                                _Socket._onData
  dart:async/zone.dart 1370:13                                           _rootRunUnary
  dart:async/zone.dart 1265:19                                           _CustomZone.runUnary
  dart:async/zone.dart 1170:7                                            _CustomZone.runUnaryGuarded
  dart:async/stream_impl.dart 341:11                                     _BufferingStreamSubscription._sendData
  dart:async/stream_impl.dart 271:7                                      _BufferingStreamSubscription._add
  dart:async/stream_controller.dart 733:19                               _SyncStreamControllerDispatch._sendData
  dart:async/stream_controller.dart 607:7                                _StreamController._add
  dart:async/stream_controller.dart 554:5                                _StreamController.add
  dart:io-patch/socket_patch.dart 1696:33                                new _RawSocket.<fn>
  dart:io-patch/socket_patch.dart 1208:14                                _NativeSocket.issueReadEvent.issue
  dart:async/schedule_microtask.dart 40:21                               _microtaskLoop
  dart:async/schedule_microtask.dart 49:5                                _startMicrotaskLoop

  package:dio/src/dio_mixin.dart 618:7    DioMixin.fetch.<fn>
  ===== asynchronous gap ===========================
  dart:async                              Future.catchError
  package:dio/src/dio_mixin.dart 609:8    DioMixin.fetch
  package:dio/src/dio_mixin.dart 466:12   DioMixin.request
  package:dio/src/dio_mixin.dart 53:12    DioMixin.get
  test\util\dio_service_test.dart 505:39  main.<fn>.<fn>.<fn>.<fn>
  ===== asynchronous gap ===========================
  dart:async                              _asyncThenWrapperHelper
  test\util\dio_service_test.dart         main.<fn>.<fn>.<fn>.<fn>

Steps to reproduce

Testing code:

//ARRANGE
final _dioAdapter = DioAdapter();
final _dio = Dio()..httpClientAdapter = _dioAdapter;

final _interceptor = DioInterceptor();
_dio.interceptors.add(_interceptor);

final firstRequestHeaders = {
  'accept-language': 'en',
  'accept': 'application/json',
  'content-type': 'application/json',
  'authorization': 'bearer ${accessToken.token}',
};

final error = getError(401, '/Test', firstRequestHeaders);

_interceptor
  ..onGet(
    '/Test',
    (request) => request.throws(
      401,
      error,
    ),
    headers: firstRequestHeaders,
  )
  ..onGet(
    '/Test',
    (request) => request.reply(200, ''),
    headers: {
      'accept-language': 'en',
      'accept': 'application/json',
      'content-type': 'application/json',
      'authorization': 'bearer ${refreshedAccessToken.token}',
    },
  )
  ..onError(error, ErrorInterceptorHandler());

//ACT
final response = await _dio.get('/Test');

Code being tested:

dio.interceptors.add(
    InterceptorsWrapper(onRequest: (requestOptions, handler) async {
      if (tenantId != null && accessToken != null) {
        requestOptions.headers['authorization'] = 'bearer ${accessToken!.token}';
      }

      handler.next(requestOptions);
    }, onError: (err, handler) async {
      if (tenantId != null && accessToken != null && err.response?.statusCode == HttpStatus.unauthorized) {
        dio.interceptors.requestLock.lock();
        dio.interceptors.responseLock.lock();

        final options = err.requestOptions;
        final refreshDio = getInstanceNoAuth();
        final newAccessToken = await _tokenRefresher.refresh(
          refreshDio: refreshDio,
          tenantId: tenantId,
          accessToken: accessToken!,
        );

        dio.interceptors.requestLock.unlock();
        dio.interceptors.responseLock.unlock();

        if (newAccessToken != null) {
          await _tenantLocalRepo.setAccessToken(newAccessToken);
          accessToken = newAccessToken;
          handler.resolve(await dio.fetch(options));
        } else {
          handler.next(err);
        }
      } else {
        handler.next(err);
      }
    }),
  );

Expected behavior

I expect any error interceptor to be called if there's an error thrown in the onGet request

System details

http_mock_adapter: ^0.2.1
dio: ^4.0.0

possible to Mocking Dio retrofit by http-mock-adapter

I am using Dio with retrofit plugin . I have called my web services like this:

      var configs = await restClient.getMainConfig({
        "versionName": "1.0",
        "currentMenuVersion": "2.0",
        "test": "test"
      });

And This is restClient.getMainConfig content:

  @POST("/loadConfig")
  Future<MainModel> getMainConfig(@Body() request);

How can I test restClient.getMainConfig?

Is there a way to have MockServerCallback's server parameter be strongly typed?

I will write test code like this:
dioAdapter.onPost("some_route", (server) => server.reply(...), data: Matchers.any);
I am very much bugged that I do not know the type of 'server'. Moreover, I don't really know what to pass to server.reply()!

What I've tried:
I've looked through the relevant docs, but I see no page on MockServerCallback. I've looked into the code and tried declaring the type of the callback that I pass into onPost(), but my linter won't allow me.

Refactoring the External API and adding a Matcher API

Description

  1. So the external api provided by MockAdapter is essentially inverting the dependency graph i.e.
final dio = Dio(BaseOptions());
final dioAdapter = DioAdapter(dio: dio);

Here the adapter is forced to be responsible for setting itself as the httpclientadapter. This creates a few issues while using DI libraries one of which is Dio needs to be instantiated before the adapter despite the adapter not depending of the Dio instance.

it would be great if instead the task of setting the adapter is externalized i.e.

final dio = Dio(BaseOptions());
final dioAdapter = DioAdapter();
dio.httpClientAdapter = dioAdapter;
  1. DioAdapter relies on an internal request matching logic which essentially relies on the entire signature to be equal. Sometimes we may desire the request matching to be simpler like say just consider path and not the entire request including headers etc.
    Current implementation can be improved by externalising matching

We can have an external interface like this

abstract class HttpRequestMatcher{
  
  bool matches(RequestOptions ongoingRequest, Request matcher);
}

and supplemented by passing an instance to the adapter like this

class DioAdapter extends HttpClientAdapter with Recording, RequestHandling {

  @override
  final HttpRequestMatcher matcher;

  DioAdapter({
    required this.matcher,
  });

This would allow us to have a few matchers like this prebaked into the library and let the users create their own custom matchers as well.

class UrlPathMatcher implements HttpRequestMatcher{

  @override
  bool matches(RequestOptions ongoingRequest, Request matcher) {
    return doesRouteMatch(ongoingRequest.path, matcher.route);
  }
}

Happy to hear your thoughts on this.

Implement Changelog

Description

Cited from keep a changelog:

What is a changelog?

A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project.

Why keep a changelog?

To make it easier for users and contributors to see precisely what notable changes have been made between each release (or version) of the project.

Who needs a changelog?

People do. Whether consumers or developers, the end users of software are human beings who care about what's in the software. When the software changes, people want to know why and how.

The content could be similar to this (inside CHANGELOG.md located in the project directory):

# Changelog

All notable changes to this project will be documented in this file.

Minor versions will be bundled with more important versions.

The format is influenced by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

Subsequently, the date entry follows **YYYY-MM-DD** format in accordance with the [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) standard.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [Unreleased]

- **Added**
  - ...
  - ...
  - ...

---

## [v0.0.1] (2020-11-09)

- **Added**
  - The MIT License

[Unreleased]: https://github.com/lomsa-dev/http-mock-adapter/compare/447829b2969300e0ff7e9d6a7c6697cd5744b632...HEAD
[v0.0.1]: https://github.com/lomsa-dev/http-mock-adapter/commit/447829b2969300e0ff7e9d6a7c6697cd5744b632

Fix linter warnings and errors

Description

There are 38~ warnings and errors in total found by linter thus far.

They should be fixed and pushed as soon as possible.

can not test queryParameters case

Description

when I add queryParameters in Dio, in that case URL is added unexpected "GET/null/{test: true}/{}"

DioError [DioErrorType.DEFAULT]: Assertion failed: "Could not find mocked route matching request for https://example.com/GET/null/{test: true}/{}"

Steps to reproduce

below one is my test code.

    test("query parameters API test", () async {
      final dio = Dio();
      final dioAdapter = DioAdapter();

      dio.httpClientAdapter = dioAdapter;

      const path = 'https://example.com';
        dioAdapter
            .onGet(
              path,
            (request) => request.reply(200,
                {'message': 'Successfully mocked GET!'}));

        // Making dio.get request on the path an expecting mocked response
        final getResponse = await dio.get(path, queryParameters: {"test": true});
        expect({'message': 'Successfully mocked GET!'},
            getResponse.data);
    });

Expected behavior

expected URL is just below

https://example.com/

Add and export DioAdapter without mockito

Description

One of the implementers of the core package functionality is ought to be DioAdapter.

It will extend HttpClientAdapter from dio package and be exported in the http_mock_adapter.dart file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.