NagRock / ts-mockito

Mocking library for TypeScript
MIT License
973 stars 93 forks source link

async arrow functions fail on target es2018 #135

Open dice14u opened 5 years ago

dice14u commented 5 years ago

Hey there I cannot seem to get async arrow functions of a class to work

    class LogClass {
      public error = async (_: string) => null;
      public warn = async (_: string) => null;
    }
    const fakeLogger = mock(LogClass);
    when(fakeLogger.error(anything())).thenResolve(null) // fakeLogger.error is not a function
    // nor does
    when(fakeLogger.warn(anything())).thenReturn(Promise.resolve(null)); //fakeLogger.warn is not a function

However

    class LogClass {
      public error = (_: string) => null;
      public async warn (_: string) { return null };
    }
    const fakeLogger = mock(LogClass);
    when(fakeLogger.error(anything())).thenReturn(null);
    when(fakeLogger.warn(anything())).thenResolve();

works. I would like it if I could use async arrow functions

NagRock commented 5 years ago

Hi @dice14u I've checked it and this should work:

class LogClass {
    public error = async (_: string) => null;
    public warn = async (_: string) => null;
}

And spec:

describe("mocking class with async functions as fields", () => {
    it("should return mocked promise value", async () => {
        // given
        const mockedWithAsyncFields = mock(LogClass);
        when(mockedWithAsyncFields.error(anything())).thenResolve("works");

        // when
        const result = await instance(mockedWithAsyncFields).error("sample");

        // then
        expect(result).toEqual("works");
    });
});

Compilation succeed and test passed.

dice14u commented 5 years ago

That is super strange that code doesn't compile for me .thenResolve("works") is resolving a null as a string Argument of type '"works"' is not assignable to parameter of type 'null'.ts(2345) Using ts-mockito 2.3.1, jest 23.3.8, typescript 3.1.3

NagRock commented 5 years ago

Because public error = async (_: string) => null; not defines return type. It creates anonymous arrow function that returns null. It has no type defined. If you want type check you can add type:

public error = async (_: string): Promise<null> => null;

Or return promise instead of null:

public error = async (_: string) => new Promise<null>(() => {});

dice14u commented 5 years ago
import { anything, instance, mock, when } from 'ts-mockito';
describe("mocking class with async functions as fields", () => {
  it("should return mocked promise value", async () => {
    class LogClass {
      public error = async (_: string): Promise<null> => null;
      public warn = async (_: string): Promise<null> => null;
    }
    // given
    const mockedWithAsyncFields = mock(LogClass);
    when(mockedWithAsyncFields.error(anything())).thenResolve(null);
    // when
    const result = await instance(mockedWithAsyncFields).error("sample");
    // then
    expect(result).toEqual(null);
  });
});

To me returns

  FAIL  asyncFailPOCTests.ts
  mocking class with async functions as fields
    × should return mocked promise value (4ms)

  ● mocking class with async functions as fields › should return mocked promise value

    TypeError: mockedWithAsyncFields.error is not a function

       8 |     // given
       9 |     const mockedWithAsyncFields = mock(LogClass);
    > 10 |     when(mockedWithAsyncFields.error(anything())).thenResolve(null);
         |                                ^
      11 |     // when
      12 |     const result = await instance(mockedWithAsyncFields).error("sample");
      13 |     // then

      at Object.it (asyncFailPOCTests:10:32)

(added together in one file for brevity of POC)

NagRock commented 5 years ago

I've copy pasted your code and it passed. Which version of ts-mockito are you using?

dice14u commented 5 years ago

Using ts-mockito 2.3.1, jest 23.3.8, ts-node 7.0.1 Thinking it might actually be something with running it under ts-node

dice14u commented 5 years ago

did some upgrades and tried running it under ts-node as its own file

import { mock, when, anything, instance } from "ts-mockito";

const main = async () => {
  class LogClass {
    public error = async (_: string) => null;
    public warn = async (_ : string): Promise<null> => null;
  }
  // given
  const mockedWithAsyncFields = mock(LogClass);
  when(mockedWithAsyncFields.error(anything())).thenResolve(null);
  // when
  const result = await instance(mockedWithAsyncFields).error("sample");

  return result;
}

main().then(() => {
    console.log('Exiting');
});
> npx tsc --version
Version 3.2.1
> npx ts-node --version
v8.0.3
> npx ts-node test.ts
(node:539444) UnhandledPromiseRejectionWarning: TypeError: mockedWithAsyncFields.error is not a function
dice14u commented 5 years ago

Figured out the difference in my tsconfig.json

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es2016",
    "lib": ["esnext.asynciterable", "es7", "dom"]
  },
}

works

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es2018",
    "lib": ["esnext.asynciterable", "es7", "dom"]
  },
}

fails

dice14u commented 5 years ago

The thing is since its a property and not a method I suppose really the work around is to do mockFunc = mock(() => null); when(mockedWithAsyncFields.error).doReturn(instance(mockFunc)); closing because ".error" is not part of the method prototype

pelepelin commented 5 years ago

I would argue against closing, instance methods are to be mockable as well as prototype methods, and it looks working for previous es target. Additionally, mocking unbound functions are not yet supported in ts-mockito, right?

dice14u commented 5 years ago

@NagRock ^ ?