Git Product home page Git Product logo

charlatan's Introduction

charlatan

pub package Build status License: MIT Maintenance

This package provides the ability to configure and return fake HTTP responses from your Dio HTTP Client. This makes it easy to test the behavior of code that interacts with HTTP services without having to use mocks.

It consists of two components and a few helper functions:

  • Charlatan - a class for configuring and providing fake HTTP responses based on HTTP method and URI template.
  • CharlatanHttpClientAdapter - an implementation of Dio's HttpClientAdapter that returns responses from a configured Charlatan instance.
  • charlatanResponse and request matching helpers - utilites for concisely matching HTTP requests and generating fake responses.

Usage

Add charlatan to your pubspec.yaml's dev_dependencies:

# pubspec.yaml
dev_dependencies:
  charlatan:

Configuring fake responses

Create an instance of Charlatan and call the corresponding configuration method for the HTTP method you want to map a request to.

You can configure fakes responses using a specific path or a URI template. You can also use the request object to customize your response. The easiest way to configure a response is with the charlatanResponse helper function.

final charlatan = Charlatan();
charlatan.whenPost('/users', charlatanResponse(body: { 'id': 1, 'bilbo' }));
charlatan.whenGet('/users/{id}', charlatanResponse(body: { 'name': 'bilbo' }));
charlatan.whenPut('/users/{id}/profile', charlatanResponse(statusCode: 204));
charlatan.whenDelete('/users/{id}', (req) => CharlatanHttpResponse(statusCode: 204, body: { 'uri': req.path }));

If you need to further customize the response, you can expand your fake response handler to include whatever you need. The only requirement is that it returns a CharlatanHttpResponse. This allows you to provide dynamic values for the status code, body, and headers in the response.

charlatan.whenPost('/users', (req) {
  final data = req.body as Map<String, Object?>? ?? {};
  final name = data['name'] as String?;
  if (name == null) {
    return CharlatanHttpResponse(
      statusCode: 422,
      body: {
        'errors': {
          'name': ['cannot be blank'],
        },
      },
    );
  }

  return CharlatanHttpResponse(
    statusCode: 201,
    body: { 'id': 1, 'name': name },
  );
});

Additionally, if you need to match requests using other properties of the request or with different logic, you can use whenMatch.

charlatan.whenMatch(
  (req) => req.method == 'GET' && req.path.toLowerCase() == '/posts',
  charlatanResponse(statusCode: 200),
);

Building a fake HTTP client

Build the CharlatanHttpClientAdapter from the Charlatan instance and then assign it to your Dio instance's httpClientAdapter.

final charlatan = Charlatan();
// ... configure fake responses ...
final dio = Dio()..httpClientAdapter = charlatan.toFakeHttpClientAdapter();

Now make HTTP requests like your normally would and they will be routed through your configured fakes.

final result = await dio.get<Object?>('/users/1');
expect(result.data, {'id', 1, 'name': 'bilbo'});

FAQ

What happens if I make a request that doesn't match a configured fake response?

You get a helpful error message like this:

Unable to find matching fake http response definition for:

GET /blahhhh

Did you configure it?

The fake http response definitions configured were:
GET /users
POST /users
PUT /users
DELETE /users

How can I configure a fake response that relies upon the result of another fake request? e.g. a POST followed by a GET that can "read its own writes"

Check out the example directory.

Contributing

If you run into a bug or limitation when using Charlatan, we'd love your help in resolving it. First, it would be awesome if you could open an issue to discuss. If we feel like we should move forward with a change and you're willing to contribute, create a fork of Charlatan and open a PR against the main repo. Keep the following in mind when doing so:

  • Prior to opening the PR, be sure to run dart format . in the root of Charlatan which will format the code so it passes CI checks
  • When opening the PR, include one of (MINOR), (MAJOR), or (NOBUMP) at the end of your PR title. Otherwise, it will fail CI. These tokens aid in the automation of our releases. Use (MINOR) to denote that your changes warrant a minor bump (they don't break the public API). Use (MAJOR) to denote that your changes warrant a major bump (they break the public API). Lastly, use (NOBUMP) to denote that your changes don't warrant a version bump (perhaps you fixed a docs typo).

charlatan's People

Contributors

samandmoore avatar celticmajora avatar btrautmann avatar

Stargazers

Gabriel Delgado avatar Karlo Verde avatar  avatar Lorenzo Pieri avatar  avatar Nicolas Schlecker avatar Elijah Luckey avatar Denys Sedura avatar Bruno Casali avatar Benyamin Beyzaie avatar Jeremiah Ogbomo avatar Chris Wickens avatar Vojtech Pavlovsky avatar Matěj Žídek avatar  avatar  avatar

Watchers

 avatar Sophia Russell avatar Isaac Zhang avatar Joseph Cherry avatar Justin Geronimo avatar Claire Davis avatar Terry Torres avatar  avatar

Forkers

btrautmann

charlatan's Issues

CharlatanResponseBuilder type-safety breaking change proposal

I think we could make a breaking change to make things more typesafe

whenDelete(
  '/users',
  charlatanResponseWith({'id': 123}, status: 200),
);

where charlatanResponseWith returns a CharlatanResponseBuilder that produces a json response with a 200 status code. then if you wanna do something fancier, you drop down to defining your own builder but we change it to require the builder to return a CharlatanHttpResponse instead of Object?. as a reminder, the reason it expects Object? right now is to support returning just a Map from the builder in the normal case where you want a 200 with a json body.

Originally posted by @samandmoore in #23 (comment)

Expose method for clearing defined fake `CharlatanHttpResponseDefinition`s

The map with CharlatanHttpResponseDefinitions is private, I would like to have an exposed method for clearing this map. I would like to use this method in tearDown so I don't have to instantiate Charlatan everytime.

Describe alternatives you've considered
Something like http-mock-adapter package has: https://github.com/lomsa-dev/http-mock-adapter/blob/eff6dc4563e30dcfe6b29bac7f7343567d61bdf6/lib/src/mixins/recording.dart#L42

Consider adding support for returning more than just body from responseBodyBuilder

i'm musing about something like this

// return this, no processing
return CharlatanResponse.raw(
  body: '{"id": 12}', // already a string
  headers: {'content-type': 'application/json'},
  statusCode: 200,
);

// return this but process the body
return CharlatanResponse(
  // still a map to be converted automatically based on the incoming accept header
  // or defaulting to json
  {'id': 12},
  statusCode: 200,
);

// current behavior would still work
return {'id': 12}; 

the reason i'm curious about this approach is that it would be nice to have a way to dynamically decide on the status code in addition to the response body. e.g. sometimes you might wanna do a 422 and not a 200.

Support additional debugging verbosity

TLDR: We should support a way (possibly via a hook on the Charlatan object) to output additional logs on things like attempted matches and failed matches such that the end user doesn't need to add break-points to debug Charlatan or pull it locally. The default verbosity would remain, so this would be opt-in per Charlatan instance.

Sam's comment:

I feel ya on the struggle of making this readable for gql or other nuanced requests.

Two thoughts:

  1. We could probably do something to include the query string here in addition to just the path. That won't help for gql unless we make some tweaks to clients to include the operation name in the query string. Which would be really useful
  2. We could add a hook on Charlatan to allow you to customize how the request prints out for this debugging use case with a solid default. Then consumers can do whatever makes sense to them to make the failed match on the request easier to interpret. Like in the case of gql maybe you can parse the operation name from the body instead of using the whole body. Not sure of a great name for this. Maybe requestDebugFormatter? Naming this is hard haha

What do you think?

Originally posted by @samandmoore in #26 (comment)

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.