Angular5 TestBed useValue seems not injecting same object instance

Viewed 2714

While writing unit tests I hit to strange case for which I don't understand where the problem is.

I've prepared short code for reproducing:

TestInjectable - simple injectable class

@Injectable()
class TestInjectable {
  testProperty = 'testValue';
}

TestComponent - small component that uses TestInjectable

@Component({
  providers: [TestInjectable],
  template: ''
})
class TestComponent {
  constructor(private injectable: TestInjectable) {
  }

  doTest() {
    return this.injectable.testProperty;
  }
}

Unit test

describe('Test TestComponent', () => {
  beforeEach(async(() => {
    let testInjectableMock: TestInjectable = new TestInjectable();
    testInjectableMock.testProperty = 'valueInMock';

    TestBed.configureTestingModule({
      providers: [{provide: TestInjectable, useValue: testInjectableMock}],
      declarations: [TestComponent]
    }).compileComponents();
  }));

  it('should do something', () => {
    let fixture: ComponentFixture<TestComponent> = TestBed.createComponent(TestComponent);
    let component: TestComponent = fixture.componentInstance;

    expect(component.doTest()).toBe('valueInMock');
  });
});

Since I have testInjectableMock to which I set valueInMock I would expect that component would return this value. The problem is that component is returning testValue which is default value and test fails with:

Expected 'testValue' to be 'valueInMock'.

It sounds like TestBed is creating another instance of TestInjectable even if I provided instance using useValue property.

providers: [{provide: TestInjectable, useValue: testInjectableMock}]

Has anyone idea if I missed something or where the catch is and how to convince TestBed to use mock instance?

3 Answers

Angular DI clones objects provided by useValue and from the looks of it, does it incorrectly:

https://github.com/angular/angular/issues/10788

You should use factory instead:

TestBed.configureTestingModule({
  providers: [{provide: TestInjectable, /*-->*/ useFactory: () => testInjectableMock}],
  declarations: [TestComponent]
}).compileComponents();

Try

describe('Test TestComponent', () => {
  let testInjectableMock: TestInjectable = new TestInjectable();

  beforeEach(async(() => {   
    TestBed.configureTestingModule({
      providers: [{provide: TestInjectable, useValue: testInjectableMock}],
      declarations: [TestComponent]
    }).compileComponents();
  }));

  it('should do something', () => {
    let fixture: ComponentFixture<TestComponent> = TestBed.createComponent(TestComponent);
    let component: TestComponent = fixture.componentInstance;
    testInjectableMock.testProperty = 'valueInMock';
    expect(component.doTest()).toBe('valueInMock');
  });
});

I suspect even with the useValue you are not getting an untainted version of your testInjectableMock.

Try this?

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [{provide: TestInjectable, useValue: new TestInjectable()}],
      declarations: [TestComponent]
    }).compileComponents().then(() => {
      const testInjectableMock = TestBed.get(TestInjectable);
      testInjectableMock.setTestProperty('valueInMock');
    });
  }));
Related