How can I manipulate providers in specific tests using ng-mocks?

1.8k Views Asked by At

I started using ng-mocks a couple weeks ago because I thought it was designed with this case in mind but for the life of me I can't figure out how to implement it.

In short, can the value of mock providers be changed in nested describes/tests after MockBuilder/MockRender have been defined?

In my application I often have components that rely on Services that provide different data depending on the context. In cases where this is static this is simple enough to test. I'll use ActivatedRoute here as an example since it's one that's giving me grief:

const mockActivatedRoute = { snapshot: { params: { id: 1 } };

describe('MyComponent', () => {
  MockInstance.scope('all');

  beforeEach(() => {
    return MockBuilder(MyComponent, MyModule)
      .provide({ provide: ActivatedRoute, useValue: mockActivatedRoute })
  });

  beforeEach(() => {
    fixture = MockRender(MyComponent);
    component = fixture.point.componentInstance;
  });

  // Imagine the component has a routeId provided by the ActivatedRoute
  it('should have an id of 1', () => { expect(component.routeId).toEqual(1); });
});

So far so good.

But after that test I want to run another suite that modifies the service; say the id is now going to be 2. I assumed this was the role of MockInstance: being able to change the provided services and such on the fly for a given test.

So to continue my example, now I'll add a nested describe within the first after my first test:

describe('MyComponent', () => {
  ...

  describe('when it inits with a different id', () => {
    MockInstance.scope();

    beforeEach(MockInstance(mockActivatedRoute, () => {
      snapshot: { params: { id: 2 } }
    }));

    it('should have an id of 2', () => { expect(component.routeId).toEqual(2); });
  });
});

This test will fail, as the new mock value was never implemented and the routeId remained unchanged.

I realize this is a poor example. I've re-written these tests so many different ways using MockInstance.remember or trying to init the service instance itself but haven't had any success. From what I can tell, it doesn't seem like MockBuilder and MockInstance are meant to be used together and there's no examples of such on the ng-mocks site. It seems the only way to build tests for cases like this is to build separate describes each with their own MockBuilder and MockRender.

Or is my best option to just make routeId public and manipulate it directly?

1

There are 1 best solutions below

0
On

MockBuilder can be used with MockInstance. It's important to notice that MockInstance works with services which haven't been initialized yet. In other words, it has effect before MockRender or TestBed.createComponent.

Once either of them has been called, you should get instances via TestBed.inject and mock them directly, for example, with ngMocks.stub and ngMocks.stubMember.

const service = TestBed.inject(Service);
let value: any;
ngMocks.stubMember(service, 'name', v => (value = v), 'set');
// value === undefined
service.name = 'fake';
// value === 'fake'

In your 2nd test, you want to change ActivatedRoute, therefore you should use it instead of mockActivatedRoute, also, because you reuse TestBed between tests, you should rely on TestBed.inject instead of MockInstance:

beforeEach(() => {
  const activatedRoute = TestBed.inject(ActivatedRoute);
  ngMocks.stub(activatedRoute, {
    snapshot: {
      params: {
        id: 2,
      },
    },
  });
});