uki00a / fresh-testing-library

Utilities for testing fresh apps
https://deno.land/x/fresh_testing_library
MIT License
22 stars 1 forks source link

Unable to test mocked function using `fn` or `mocked` #62

Closed Elliot-Evans-95 closed 9 months ago

Elliot-Evans-95 commented 10 months ago

Hello,

I have been trying to mock my 3rd party functions in a fresh handler and I have used createHandlerContext test my handler.

I am able to get back fields such as params and status as expected but the value are not what I would expect and I think there is a missing step in my test setup.

Take this example here:

describe("Flags Page", () => {
  const viewPageUrl = `http://localhost:8000/flags/view`;

  it("return a status of 200 via GET method", async () => {
    const req = new Request(viewPageUrl);
    const ctx = createHandlerContext(req, { manifest });
    const res = await handler.GET!(req, ctx);

    assertEquals(ctx.params, {});
    assertEquals(res.status, 200);
  });
});

Here the status will not return 200 because in the handler is a auth check. I want to mock out that auth check as its a 3rd party auth. So I am now adding this:

    const mockedGetSessionId = mocked(getSessionId)
    mockedGetSessionId.mockReturnValue("fake-session-id");

Therefore I would expect it to now work and be a 200 but it doesn't work. I think there is a missing step where in the jest world I would have to mock out the module in order for the mock function to work as expected, example:

  jest.mock('kv_oauth', () => {
    // would require actual here then mock out the function or have a hoist function that I can change per test
    getSessionId: jest.fn().mockReturnValue("fake-session-id")
  });

but Jest doesn't support ES Modules and even with jest.unstable_mockModule it won't work as it would require the Jest runner to run the test and then the Jest setup object too.

Am I missing something here or am I right in thinking there needs to be a jest.unstable_mockModule available?

Many thanks for the library and I hope you the above makes sense!

cdoremus commented 10 months ago

Can you point to a repo that includes the code-under-test and your test code including the imports?

uki00a commented 10 months ago

@Elliot-Evans-95 Thanks for reporting the issue! There may be several workarounds for it.

1. Use spyOn() to stub KV OAuth's helpers

First, prepare a module that exposes helpers:

// kv_oauth_helpers.ts
import {
  createGitHubOAuthConfig,
  createHelpers,
} from "kv_oauth";

export const helpers = createHelpers(
  createGitHubOAuthConfig(),
);

In the handler code, use helpers via the above module:

// routes/some_route.ts
import { helpers } from "./path/to/kv_oauth_helpers.ts";
import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req, ctx) {
    const maybeSessionId = await helpers.getSessionId(req);
    if (maybeSessionId) {
      // ..
    } else {
      // ...
    }
  },
};

Then the test code can stub getSessionId() with spyOn() as follows:

import { spyOn } from "$fresh-testing-library/expect.ts";
import { afterAll } from "$std/testing/bdd.ts"
import { helpers } from "./path/to/kv_oauth_helpers.ts";
import { handler } from "./path/to/routes/some_route.ts";

describe("Flags Page", () => {
  const viewPageUrl = `http://localhost:8000/flags/view`;
  const mockedGetSessionId = spyOn(helpers, "getSessionId").mockReturnValue("fake-session-id");
  afterAll(() => mockedGetSessionId.mockRestore());

  it("return a status of 200 via GET method", async () => {
    // ...
  });
});

2. Use KV OAuth's helpers via ctx.state

If you want to test without mocking/stubbing, you can also use helpers via fresh's ctx.state. First, prepare a middleware that configures ctx.state:

// routes/_middleware.ts
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import {
  createGitHubOAuthConfig,
  createHelpers,
} from "kv_oauth";

export const helpers = createHelpers(
  createGitHubOAuthConfig(),
);

export interface State {
  oauth: typeof helpers;
}

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext<State>,
) {
  ctx.state.oauth = helpers;
  const resp = await ctx.next();
  return resp;
}

In the handler code, You can call getSessionId() via ctx.state:

// routes/some_route.ts
import { helpers } from "./path/to/kv_oauth_helpers.ts";
import { Handlers } from "$fresh/server.ts";
import type { State } from "./_middleware.ts";

export const handler: Handlers<void, State> = {
  async GET(req, ctx) {
    const maybeSessionId = await ctx.state.oauth.getSessionId(req);
    if (maybeSessionId) {
      // ..
    } else {
      // ...
    }
  },
};

In the test code, ctx.state can be set using state option of createHandlerContext:

import type { State } from "./path/to/routes/_middleware.ts";
import { handler } from "./path/to/routes/some_route.ts";

describe("Flags Page", () => {
  const viewPageUrl = `http://localhost:8000/flags/view`;

  it("return a status of 200 via GET method", async () => {
    const req = new Request(viewPageUrl);
    const state: State = { oauth: createFakeKvOAuthHelpers() };
    const ctx = createHandlerContext<void, State>(req, { manifest, state });
    const res = await handler.GET!(req, ctx);

    assertEquals(ctx.params, {});
    assertEquals(res.status, 200);
  });
});
Elliot-Evans-95 commented 10 months ago

Hello gents, sorry for the late response.

@cdoremus - the repo is private at the moment but I could look to extract it out so there is a public copy.

@uki00a - I will try out those suggestions and respond on here once I have tried them out 😄

Elliot-Evans-95 commented 9 months ago

Hello @uki00a

Wonderful news, using your example of spyOn() as a springboard I was able to get this working.

The big difference is the package versions: "kv_oauth" - from "0.9.1" to "0.10.0" "fresh-testing-library" - from "0.11.1" to "0.12.0"

After the above changes I am able to use this:

const mockedGetSessionId = spyOn(oauth2Client, "getSessionId").mockResolvedValue("fake-session-id");

The example from the docs helps to better understand how to use the createHelpers from your example: https://deno.land/x/deno_kv_oauth@v0.10.0/lib/create_helpers.ts?source

Elliot-Evans-95 commented 9 months ago

In addition apologies for the late response, happy for this to be closed 👍

uki00a commented 9 months ago

I'm glad to hear it worked well :+1: