ngneat / spectator

🦊 🚀 A Powerful Tool to Simplify Your Angular Tests
https://ngneat.github.io/spectator
MIT License
2.07k stars 178 forks source link

Mocking of abstract services #247

Open Willey1986 opened 4 years ago

Willey1986 commented 4 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

At the moment when passing an abstract type to the mocks array the following error is shown when running ng test

Cannot assign an abstract constructor type to a non-abstract constructor type.

The only working methods I have found so far to mock this are:

manually creating a jasmine spy

createComponentFactory({
  ...
  providers: [
    {provide: AbstractType, useValue: jasmine.createSpyObj(AbstractType.name, ['methodToMock'])}
  ]
})

Problem here is that later in my test I have to the jasmine way to use the mock instead of the spectator way. Using the spectator way results in an error.

jasmine way

spectator.get(AbstractType).methodToMock.and.returnValue()

spectator way

// Throws error 'andReturn does not exist'
spectator.get(AbstractType).methodToMock.andReturn() 

Problem with this solution is that I have to remember which mock was created how and have to use one way or the other. This might lead to confusion when a different team member is extending my test.

Implement the abstract class and pass the implementation to the mocks array.

class ConcreteType extends AbstractType {
 ...
}

createComponentFactory({
  ...
  mocks: [
    ConcreteType
  ]
})

Problems

Expected behavior

What is the motivation / use case for changing the behavior?

Consistent less error prone tests.

Environment

NOT RELEVANT
Willey1986 commented 4 years ago

I just figured out that the second solution from the description above Implement the abstract class and pass the implementation to the mocks array. isn't working at all.

NullInjectorError: StaticInjectorError(DynamicTestModule)[MyComponent -> AbstractType]: 
  StaticInjectorError(Platform: core)[MyComponent -> AbstractType]: 
    NullInjectorError: No provider for AbstractType!

EDIT1: The following code works instead which is even worse because it needs an implementation of the abstract type and a manual entry in the providers array.

class ConcreteType extends AbstractType {
  ...
}

createComponentFactory({
  ...
  providers: [
    {provide: AbstractType, useValue: createSpyObject(ConcreteType)}
  ]
})
NetanelBasal commented 4 years ago

The options available are in this file:

https://github.com/ngneat/spectator/blob/f0e5d7a887114cfe6e1c2f42b2c6e0629d60c7c9/projects/spectator/test/injection-and-mocking.spec.ts#L41

Willey1986 commented 4 years ago

Which means there is no option for mocking of abstract services?

The provided example only shows how to provide an implementation for an abstract service. The provided service (in the example) is not a jasmine spy so I cannot mock its behaviour.

spectator.get(AbstractQueryService).someMethod.andReturn(...);
tstackhouse commented 4 years ago

This is also applicable to mocking said providers, e.g. mockProvider(AbstractQueryService) will fail with the same error, thus forcing me to have a stub class in my test files instead of automatically having a stubbed provider.