I'm stuck on this again and although this thread (MockService still causes error: Cannot read property 'subscribe' of undefined) is exactly about the same thing it still doesn't resolve my problem.
I do have a component which calls a simple function on ngOnInit:
ngOnInit() {
this.getModules();
}
getModules() {
this.configService.getModulesToDisplay().subscribe(modules => {
this.modulesToDisplay = modules;
}
);
}
I want to test two things:
Is getModules called on ngOnInit
and
Is this.modulesToDisplayed reassigned when it gets some result
So I mocked my service but the first test still fails with the TypeError 'Cannot read property 'subscribe' of undefined'.
I moved my mocked Service to all different areas since I do guess the mock isn't available when the test starts to build the component. But I still couldn't make it out. My Test looks like:
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
let configServiceMock: any;
beforeEach(async(() => {
configServiceMock = jasmine.createSpyObj('ConfigService', ['getModulesToDisplay']);
configServiceMock.getModulesToDisplay.and.returnValue( of(['module1', 'module2']) );
TestBed.configureTestingModule({
declarations: [
NavigationComponent
],
imports: [
RouterTestingModule,
HttpClientTestingModule
],
providers: [
{ provide: ConfigService, useValue: configServiceMock },
],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
}).compileComponents();
beforeEach(() => {
// configServiceMock.getModulesToDisplay.and.returnValue( of(['module1', 'module2']) );
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
});
}));
I removed fixture.detectChanges() to have full control over when ngOnInit should be called and so my tests look like:
it('should call getModulesToDisplay one time on ngOninit()', () => {
const spyGetModules = spyOn(component, 'getModules');
component.ngOnInit();
expect(spyGetModules).toHaveBeenCalledTimes(1);
});
The first test fails with the Cannot read subscribe error. But the second one passes with the correct mockvalue being used.
it('should assign result to modulesToDisplay', () => {
component.getModules();
expect(component.modulesToDisplay.length).toBeGreaterThan(0);
});
Any hints on what am I still missing are highly appreciated!
Rather than writing jasmine spy in each spec file, create a reusable Mock file
export class MockConfigService{
getModulesToDisplay(){
return of({
// whatever module object structure is
})
}
}
and in it block:
it('should call getModulesToDisplay one time on ngOninit()', () => {
spyOn(component, 'getModules').and.callThrough();
component.ngOnInit();
expect(component.getModules).toHaveBeenCalledTimes(1);
});
Related
I'm using: NestJS and I have a class exported using namespace. Right after the export namespace I have the NestJS #Injectable decorator and I get the following error when I try to run the test: × Expected '{', got 'namespace'
Without #Injectable the test runs without problems, but I need Injectable.
Class with Injectable
export namespace SearchCase {
#Injectable()
export class SearchCase {
constructor(private casesRepository: InterfaceRepository<Case.Case>) { }
async execute(request: RequestSearchCase): Promise<ResponseSearchCase> {
const searchResult = await this.casesRepository.search(request.search);
return {
searchResult,
};
}
}
}
Test
describe('Search Case', () => {
it('should be able return a case with substring', async () => {
const casesRepository = new InMemoryCaseRepository();
const searchCase = new SearchCase.SearchCase(casesRepository);
const createCase = new Case.Case({
utente: 'utente test',
caseOrigin: 'case origin test',
reportingDate: new Date(),
reporterName: 'Emanuela Xavier',
disease: 'disease test',
})
await casesRepository.create(createCase);
const response = await searchCase.execute({
search: 'Ema'
});
expect(response.searchResult.length).toBe(1);
expect(response.searchResult[0].reporterName).toContain('Ema');
});
});
ERROR
Error shown when I run the test
Removing #Injectable the test works without problem, but I need to use it.
I was using SWC/Jest instead of ts-jest, when I switched to ts-jest in my jest.config.ts the tests came back working even though I was using #Injectable.
I still don't understand why with #swc/jest it's failing, but for now it's working, when I have time I'll research more to find out the error.
Configuration with #swc/jest not working
Archive: jest.config.ts
"transform": {
"^.+\\.(t|j)s$": ["#swc/jest"]
}
Configuration with #SW/jest that work
Archive: jest.config.ts
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
I am trying to inject a dependency to an exception filter.
Here is my dependency:
#Injectable()
export class SmsService {
constructor() {}
async create() {
console.log('sms created');
}
}
And its module:
#Module({
providers: [SmsService],
exports: [SmsService],
})
export class SmsModule {}
And my exception filter is here:
#Catch(InternalServerErrorException)
export class InternalServerErrorFilter implements ExceptionFilter {
#Inject(SmsService)
private readonly smsService: SmsService;
async catch(exception: InternalServerErrorException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// smsService is undefined so create property is undefined
await this.smsService.create();
response.status(exception.getStatus()).send({
message: 'Something went wrong our side.',
statusCode: exception.getStatus(),
});
}
}
My app module :
#Module({
imports: [SmsModule, MailsModule],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_FILTER,
useClass: InternalServerErrorFilter,
},
],
})
export class AppModule {}
My main.ts file :
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new InternalServerErrorFilter());
await app.listen(3000);
}
bootstrap();
Finally my app service is here.
#Injectable()
export class AppService {
getHello(): string {
throw new InternalServerErrorException();
}
}
It just throws internal server error exception for executing internal server error filter.
When I send a request to http://localhost:3000 it throws an error like below:
/* await this.smsService.create();
^
TypeError: Cannot read properties of undefined (reading 'create')
*/
My application starts successfully so it seems like no dependency error.
If I recall, enhancers bound by useGlobal*() have precedence over APP_* providers (need to double check that). By using new you are in charge of setting everything that that class instance will need, regardless if it has #Inject() decorators on properties or in the constructor or not. The #Inject() just sets metadata that Nest can read so it knows what to set during class instantiation. So, when you pass new InternalServerErrorFilter() and don't ever set smsService, you get an error at runtime because that service is never defined, only declared.
If you're going to use global enhancers, I'd highly suggest keeping just one global enhancer binding type, and would even more highly suggest just using the APP_* bindings because they're easier to keep inline with your application and your e2e tests, plus Nest can do DI on enahncers bound via APP_*
I have a simple service with an Injection token that is used to provide some kind of configuration for the service. It's all working as expected. Although I wasn't able to test all the possible scenarios in single spec file. For some reason I'm not allowed to override the Injection token defined in providers.
token.config.ts
export interface MyConfig = {
test: boolean;
}
export const ConfigToken = new InjectionToken<MyConfig>('MyConfig');
token.service.ts
#Injectable()
export class TokenRefreshService {
public autoRefreshStarted = false;
constructor(
#Inject(ConfigToken) private config: MyConfig
) {}
public initAutoRefresh(): void {
if ( this.config.test === true ) {
this.autoRefreshStarted = true;
}
}
}
token.service.spec.ts
describe('TokenRefreshService (with-auto-refresh)', () => {
let service: TokenRefreshService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
{
provide: ConfigToken,
useValue: {}
},
TokenRefreshService
]
});
service = TestBed.get( TokenRefreshService );
});
it('should create an instance', () => {
expect( service ).toBeDefined();
});
it('should not start auto-refresh', () => {
service.initAutoRefresh();
expect( service.autoRefreshStarted ).toBeFalsy();
});
it('should start auto-refresh', () => {
TestBed.overrideProvider( ConfigToken, { useValue: { test: true } }); /// doesn't override the config token at all ///
service.initAutoRefresh();
expect( service.autoRefreshStarted ).toBeTruthy();
});
});
I would like to test scenarions when no config is provided to service or when config with an incorrect data is provided and so on. Therefore I really need a way to somehow override the injection token that is passed to the TokenService. But no matter what I tried, it just keeps returning the same data that are defined in the TestBed.configureTestingModule.
Am I doing something wrong? Or is there an easier way to do this?
Replace
imports: [
{
provide: ConfigToken,
useValue: {}
},
TokenRefreshService
]
with:
providers: [
{
provide: ConfigToken,
useValue: {}
},
TokenRefreshService
]
I guess importing a service never works
mockData.js
var userInfo = {
URLs: {
AppURL: "A"
},
EncryptedBPC: "B"
};
karma.config.js
config.set({
basePath: '',
files:['mockData.js' ],
.....
ComponentDetailsComponent:
.....some imports
import { ComponentDetailsService } from '../component-details.service';
declare var userInfo: any;
#Component({
.....more code
rfxFilter() {
return userInfo.URLs.AppURL;
}
}
Spec:
describe('ComponentDetailsComponent', () => {
let subject:any;
let fixture: ComponentFixture<ComponentDetailsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ ComponentDetailsComponent ],
providers: [{ provide: ComponentDetailsService, useClass:
ComponentDetailsServiceStub }],
});
fixture = TestBed.createComponent(ComponentDetailsComponent);
subject = fixture.componentInstance;
});
it('should return X', () => {
subject.userInfo = {
URLs: {
AppURL: "X"
},
EncryptedBPC: "Y"
};
let result = subject.rfxFilter();
expect(result).toBe("X");
});
});
Output:
ReferenceError: userInfo is not defined
I have made it work by creating a method inside the component which will return userInfo global variable.
getuserInfo():any{
return userInfo;
}
And mocking that method in spec:
let m = {
URLs: {
AppURL: "mockvalueAppURL",
},
EncryptedBPC: "mockEncryptedBPC",
};
let spy = spyOn(subject, 'getuserInfo').and.returnValue(m);
Is it not possible to mock such global variables without having to encapsulate it within methods and then mocking the method instead of variable? I would like to keep the application code untouched when written by somebody else.
You can't access any variables of any other files. You can't mock imports either. Your best friend is DI, as you can provide mock class in place of original for testing.
You will have to mock the service that provides the data, not having the data in a separate file. The only way would be to export JSON, or object and use the exported object.
TestBed.configureTestingModule({
imports: [
],
declarations: [
ComponentDetailsComponent,
],
providers: [
{
provide: RealService,
useExisting: StubService,
},
],
}).compileComponents();
And implement the stub as this.
class StubService implements Partial<RealService> {
getuserInfo() {
return { ...mockedData };
}
}
Note:
If you are dealing with mocking HTTP calls use HttpTestingController.
I am attempting to spy on the login() function of my auth.service from within my login.component and I have tried to simplify my spies.
I am receiving the error:
TypeError: Cannot read property 'login' of undefined
at LoginComponent.onSubmit src/app/components/login/login.component.ts:28:30)
login.spec.ts
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent],
imports: [FormsModule, HttpClientTestingModule],
providers: [{provide: AuthService, useValue: mockAuthService}]
}).compileComponents();
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
mockAuthService = jasmine.createSpyObj(['login', 'logout']);
});
I solved the issue by moving the line:
mockAuthService = jasmine.createSpyObj(['login', 'logout']); to the top of the beforeEach block.
Create a mock that contains your service values and provide it to your testbed (like you are already doing) :
const mockAuthService = {
login: () => of(null),
logout: () => of(null)
};
Now, you can spy on the methods :
spyOn(component['authService'], 'login');
...
expect(component['authService'].login).toHaveBeenCalled();