Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for middleware testing on dart_frog_test #1101

Open
mtwichel opened this issue Oct 6, 2023 · 2 comments
Open

feat: Add support for middleware testing on dart_frog_test #1101

mtwichel opened this issue Oct 6, 2023 · 2 comments
Labels
feature A new feature or request

Comments

@mtwichel
Copy link
Contributor

mtwichel commented Oct 6, 2023

Description

A clear and concise description of what the bug is.

Steps To Reproduce

  1. In a new Dart Frog project, Create a new _middleware.dart with
import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
  return handler.use(provider<String>((_) => 'Hello World'));
}
  1. Create a new test file called _middleware_test.dart with these contents
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_test/dart_frog_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../../routes/_middleware.dart';

class _MockResponse extends Mock implements Response {}

void main() {
   test('injects Hello World', () async {
      final context = TestRequestContext(path: '/');

      String? value;
      Response handler(RequestContext context) {
        value = context.read<String>();
        return _MockResponse();
      }

      final newHandler = middleware(handler);
      await newHandler(context.context);

      expect(
        value,
        equals('Hello World'),
      );
    });
}

3.Run the test and you'll get

type 'Null' is not a subtype of type 'RequestContext'
package:dart_frog_test/src/test_request_context.dart 4:7  _MockRequestContext.provide
package:dart_frog/src/provider.dart 8:41                  provider.<fn>.<fn>
test/routes/_middleware_test.dart 43:23                   main.<fn>.<fn>

Expected Behavior

I would expect the test to pass.

Additional Context

I believe this is an issue because the underlying RequestContext returned from context.context is a _MockRequestContext that has no stub implementation for .provide, so when the provider middleware is called, it produces that error.

RequestContext get context {
final request = Request(
method.name.toUpperCase(),
Uri.parse('$basePath$path'),
headers: headers,
body: body,
);
when(() => _requestContext.request).thenReturn(request);
return _requestContext;
}

Another way to test middleware proposed by @felangel is here: https://github.com/VeryGoodOpenSource/dart_frog/blob/main/examples/kitchen_sink/test/routes/_middleware_test.dart

This is a nice solution because it doesn't require .read to be stubbed, but I'm not sure how it could be generalized in a package like this.

Also happy to be shown how I'm doing it wrong because I hope I am 😆

Thanks 💙

@mtwichel mtwichel added the bug Something isn't working as expected label Oct 6, 2023
@renancaraujo renancaraujo added feature A new feature or request and removed bug Something isn't working as expected labels Oct 10, 2023
@renancaraujo renancaraujo changed the title fix: dart_frog_test has trouble testing middleware feat: Add support for middleware testing on dart_frog_test Oct 10, 2023
@renancaraujo
Copy link
Contributor

Hello @mtwichel ,

Thanks for opening an issue!

Middleware testing was not in scope for the first version of dart_frog_auth, but should be prioritized for one of the next versions.

@mtwichel
Copy link
Contributor Author

mtwichel commented Oct 11, 2023

Sounds good @renancaraujo!

For your consideration, since opening this issue I've been trying to solve it for my project and settled on this implementation of TestRequestContext:

import 'package:dart_frog/dart_frog.dart';

class TestRequestContext implements RequestContext {
  TestRequestContext({
    this.path = '/',
    this.basePath = 'https://example.com',
    this.method = HttpMethod.get,
    this.headers,
    this.body,
  });
  String path;
  String basePath;
  HttpMethod method;
  Map<String, String>? headers;
  Object? body;

  Map<String, dynamic> _dependencies = {};

  @override
  RequestContext provide<T extends Object?>(T Function() create) {
    final dependency = create();
    _dependencies[dependency.runtimeType.toString()] = dependency;
    return this;
  }

  void mockProvide<T>(T dependency) => _dependencies[T.toString()] = dependency;

  @override
  T read<T>() {
    final dependency = _dependencies[T.toString()];
    if (dependency is! T) {
      throw Error();
    }
    return dependency;
  }

  @override
  Request get request => Request(
        method.name.toUpperCase(),
        Uri.parse('$basePath$path'),
        headers: headers,
        body: body,
      );
}

It has a few advantages imo

  1. It provides implementations for read and provide, so it can easily be used to test middleware.
    Example
test('injects a RequestLogger', () async {
      final context = TestRequestContext();
      RequestLogger? logger;
      Future<Response> handler(RequestContext context) async {
        logger = context.read<RequestLogger>();
        return Response();
      }

      final newHandler = middleware.middleware(handler);
      await newHandler(context);
      expect(logger, isNotNull);
    });
  1. It implements RequestContext directly so you don't have to do context.context when calling your handlers (which I always thought was confusing).
  2. It's properties aren't final so that you can create an object in a setUp function but then modify specific properties in the individual tests.
  3. It would be a breaking change because of point 2, but it does share the same constructor (although I added a default to the path parameter as it doesn't really matter what the path is for middleware).

Just my experience working with it so far. 💙

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A new feature or request
Projects
Status: Backlog
Development

No branches or pull requests

2 participants