8

I'm trying to stub a class instance using sinon.createStubInstance but I'm getting an error stating that a private member variable is missing. Of course I can't explicitly set it either because it's a private member.

Example classes:

class Foo {
  private readonly bar: string;

  constructor(bar: string) {
    this.bar = bar;
  }
}

class Parent {
  foos: Foo[];

  constructor(foos: Foo[]) {
    this.foos = foos;
  }
}

And in the test I'm writing a beforeEach block:

beforeEach(function () {
  const stubFoo = sinon.createStubInstance(Foo);

  const stubParent = sinon.createStubInstance(Parent);
  stubParent.foos = [stubFoo]; // Tslint error here
});

The Tslint error is:

Property 'bar' is missing in type 'SinonStubbedInstance' but required in type 'Foo'

For the record I'm using Typescript v3.0.3 and Sinon v7.4.1.

Ricardo Rocha
  • 14,612
  • 20
  • 74
  • 130
Mark
  • 103
  • 6
  • Works quite well in [a Stackblitz](https://stackblitz.com/edit/typescript-4yy5yr). Your version of TypeScript is quite old (we are currently at 3.6.3), so I would advise to update it. – frankie567 Sep 25 '19 at 14:36
  • @frankie567 That's missing the key line on the bottom `stubParent.foos = [stubFoo];`. Thanks for the heads up about the TypeScript version. – Mark Sep 25 '19 at 15:11
  • Oh yeah, sorry, I've missed that. Problem here is that `stubFoo` is of type `SinonStubbedInstance`, which is not a sub-type of `Foo`, thus, not assignable. What you could do is to cast it `stubParent.foos = [stubFoo]` ; not very elegant but, well, test setups can be cumbersomes. – frankie567 Sep 25 '19 at 15:25
  • Unfortunately in this case it requires `stubParent.foos = [stubFoo]` because without the initial cast to `` it still complains that the private property is not defined. If I make the member public though things just work, `SinonStubbedInstance` is a valid sub-type of `Foo`. – Mark Sep 25 '19 at 16:13

3 Answers3

1

I personally like this solution found by a Github user (paulius-valiunas):

if you just want to use this in a couple places, a simpler and more readable solution would be to use the type alias StubbedClass<T> = SinonStubbedInstance<T> & T directly. For example:

export type StubbedClass<T> = SinonStubbedInstance<T> & T;
const myStub = sinon.createStubInstance(MyClass) as StubbedClass<MyClass>;

or just:

const myStub = sinon.createStubInstance(MyClass) as SinonStubbedInstance<MyClass> & MyClass;

way cleaner and simpler than other suggestions!

Guilherme Matuella
  • 2,193
  • 1
  • 17
  • 32
0

There is actually a nicer workaround than force-casting your stub, posted on github here:

import { createStubInstance, StubbableType, SinonStubbedInstance, SinonStubbedMember } from 'sinon';

export type StubbedClass<T> = SinonStubbedInstance<T> & T;

export function createSinonStubInstance<T>(
  constructor: StubbableType<T>,
  overrides?: { [K in keyof T]?: SinonStubbedMember<T[K]> },
): StubbedClass<T> {
  const stub = createStubInstance<T>(constructor, overrides);
  return stub as unknown as StubbedClass<T>;
}

You can then use it as such:

beforeEach(function () {
  const stubFoo: StubbedClass<Foo> = createSinonStubInstance(Foo);
  const stubParent: StubbedClass<Parent> = createSinonStubInstance(Parent);

  stubParent.foos = [stubFoo]; // No more Tslint error here
});

It worked for me on a different configuration, and bring all the autocompletion for both my classes and Sinon's stubbed instance.

Bonlou
  • 472
  • 2
  • 9
0

You can do this for a quick work around:

stubParent.foos = [stubFoo as unknown as Foo];

I still haven't found any elegant way to solve this as of the moment of writing this.

Pevin
  • 61
  • 2