spectator icon indicating copy to clipboard operation
spectator copied to clipboard

Mocking of abstract services

Open bravewasp opened this issue 5 years ago • 4 comments

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

  • For abstract types with many methods the test gets polluted with unnecessary code (all abstract methods need to be "implemented"
  • The implementation has to be done in every test or a central MockImplementation must be provided.
    • Both solutions aren't ideal because either we get a lot of duplicated code or every team member must know that the MockImplementation exists.

Expected behavior

  • The mocks array can handle abstract types OR
  • createSpyObject() can create mocks of abstract types OR
  • other solution

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

Consistent less error prone tests.

Environment

NOT RELEVANT

bravewasp avatar Dec 13 '19 08:12 bravewasp

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)}
  ]
})

bravewasp avatar Dec 13 '19 09:12 bravewasp

The options available are in this file:

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

NetanelBasal avatar Dec 17 '19 13:12 NetanelBasal

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(...);

bravewasp avatar Dec 17 '19 14:12 bravewasp

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.

tstackhouse avatar Feb 19 '20 16:02 tstackhouse