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

Interface mocks cannot be bound in inversify containers #222

Open
nseniak-iziwork opened this issue Nov 5, 2021 · 5 comments
Open

Interface mocks cannot be bound in inversify containers #222

nseniak-iziwork opened this issue Nov 5, 2021 · 5 comments

Comments

@nseniak-iziwork
Copy link

We use inversify for IOC (https://inversify.io/). Injecting mocks is an important testing use case for us. However, we found that interface mocks cannot be properly bound in inversify contexts.

Here's code combining inversify and ts-mockito that shows the problem:

import "reflect-metadata";
import { Container } from "inversify";
import { instance, mock } from "ts-mockito";

const container = new Container();

// Binding a class mock: works as expected
class Bar {}

const barMock = mock(Bar);
container.bind<Bar>("Bar").toConstantValue(instance(barMock));
const barMockGet = container.get<IFoo>("Bar"); // => barMock (expected)

// Binding an interface mock: doesn't work
interface IFoo {
  prop: string;
}

const ifooMock = mock<IFoo>();
container.bind<IFoo>("IFoo").toConstantValue(instance(ifooMock));
const ifooMockGet = container.get<IFoo>("IFoo"); // => null (should be ifooMock)

After looking into the inversify code, it seems that the problem is that inversify.isPromise incorrectly returns true for interface mocks, which makes invetsify try to resolve the mock, resulting in a null value:

import { isPromise } from "inversify/lib/utils/async";

const isBarMockPromise = isPromise(barMock); // => false (expected)
const isIfooMockPromise = isPromise(ifooMock); // => true (should be false)

The inversify code for isPromise is here: https://github.com/inversify/InversifyJS/blob/master/src/utils/async.ts

@NagRock
Copy link
Owner

NagRock commented Nov 17, 2021

ts-mockito is using Proxy for interfaces mocking. So when InversifyJS wants to check if then is a function:

function isPromise(object) {
    var isObjectOrFunction = (typeof object === 'object' && object !== null) || typeof object === 'function';
    return isObjectOrFunction && typeof object.then === "function";
}

ts-mockito immediately creates mock function for then - thats why InversifyJS thinks its a mock. And ts-mockito needs to do that because it cannot figure out if its a part of interface or not (interfaces are not available in runtime).

@NagRock
Copy link
Owner

NagRock commented Nov 17, 2021

As a work around you can do when(mockedFoo['then']).thenReturn(undefined); - but I think its ugly.

@MaxNamazov
Copy link

Faced this problem today.
Currently resolved with tiny wrapper function based on @NagRock suggestion

function mockInterface<T>(): T {
  const mocked = mock<T>();
  when((mocked as any).then).thenReturn(undefined);
  return mocked;
}

@Diveafall
Copy link

@NagRock Just encountered the same problem, but unfortunately I didn't see this post soon enough so had to spend a lot of time debugging. Since then is so important for identifying a Promise perhaps we should include it in the list of excludedFunctionNames inside MockableFunctionsFinder?

@weyert
Copy link

weyert commented Dec 4, 2023

Yeah, also spend a long time to figure this issue out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants