THE SITUATION:
In my app.component there is the login function. It is working properly and I get the user data from the API.
I need to store this data as a global variable to be able to access it throughout the app.
I thought that sharing a service was the way to do it, but unfortunately is not working as I thought.
THE CODE:
The service:
import { Injectable } from '#angular/core';
#Injectable()
export class LoginService {
public accountInfo;
setUserData(value)
{
this.accountInfo = value;
}
getUserData() {
return this.accountInfo;
}
}
The app.component:
loginSubmit()
{
// Function reduced to what is essential to the question
this.userService.submitLogin(email, password)
.subscribe((response) => {
if(response.result == 1)
{
this.loginService.setUserData(response.account_info);
var justTesting = this.loginService.getUserData();
// Getting the proper result back
console.log(justTesting);
}
}, (error) => {
console.log(error);
});
}
Trying to access the user data from another component:
this.accountInfo = this.loginService.getUserData();
console.log(this.accountInfo);
The result is undefined. Probably because this.accountInfo (in the service) is instantiated again when the service is called from the other component...
THE QUESTION:
How can I store some data as a global variable? Can it be done by sharing a service?
Thanks!
Ionic provides a method for storing global variables with the Storage plugin. (cordova-sqlite-storage). Storage will attempt to use IndexedDB, WebSQL, and localstorage, in that order.
First add it to the providers list.
In src/app.module.ts
import { Storage } from '#ionic/storage';
#NgModule({
declarations: [
// ...
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
// ...
],
providers: [
Storage
]
})
export class AppModule {}
And then inject it in your component
import { Storage } from '#ionic/storage';
export class MyApp {
constructor(storage: Storage) {
// set a key/value
storage.set('name', 'Max');
// Or to get a key/value pair
storage.get('name').then((val) => {
console.log('Your name is', val);
})
}
}
#Matt answer is properly working and it offer a good solution to the problem.
I also found what was the problem in my code: The service had to be a singleton. I was instead injecting the service in the providers of the component.
And that was the problem because the service was instantiated again and thus public accountInfo lost his previous content.
You can share a service to store a global variable - but has to be a singleton - you need to inject it only once in the app.module.ts:
providers: [ LoginService ]
Related
I am writing test cases in my email-service.spec.ts file
my email-service file
#Injectable()
export class EmailSubscriptionService {
private nodeTokenCache;
private result;
constructor(#InjectRepository(ConsumerEmailSubscriptions) private readonly emailSubscriptions: Repository<ConsumerEmailSubscriptions>,
#InjectRepository(EmailSubscriptions) private readonly emailSubscriptionLegacy: Repository<EmailSubscriptions>,
#InjectRedisClient('0') private redisClient: Redis.Redis,
private readonly config: ConfigService, private http: HttpService,
private readonly manageSavedSearchService: ManageSavedSearchService) {
}
my email-service.spec.ts file
import { RedisService } from 'nestjs-redis';
import { ConfigService } from '#nestjs/config';
import { HttpService } from '#nestjs/common';
import { ManageSavedSearchService } from './../manage-saved-search/manage-saved-search.service';
describe('EmailSubscriptionService', () => {
let service: EmailSubscriptionService;
let entity : ConsumerEmailSubscriptions;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports:[RedisModule],
// https://github.com/nestjs/nest/issues/1229
providers: [EmailSubscriptionService,
{
// how you provide the injection token in a test instance
provide: getRepositoryToken(ConsumerEmailSubscriptions),
// as a class value, Repository needs no generics
useClass: Repository,
// useValue: {
// }
},
{
provide: getRepositoryToken(EmailSubscriptions),
useClass: Repository,
},
RedisService,
// {
// provide : RedisService,
// useClass: Redis
// },
ConfigService, HttpService, ManageSavedSearchService
],
}).compile();
service = module.get<EmailSubscriptionService>(EmailSubscriptionService);
// entity = module.get<Repository<ConsumerEmailSubscriptions>>(getRepositoryToken(ConsumerEmailSubscriptions));
});
it('should be defined', async () => {
expect(service).toBeDefined;
});
});
result ---->
Nest can't resolve dependencies of the EmailSubscriptionService (ConsumerEmailSubscriptionsRepository, EmailSubscriptionsRepository, ?, ConfigService, HttpService, ManageSavedSearchService). Please make sure that the argument REDIS_CLIENT_PROVIDER_0 at index [2] is available in the RootTestModule context.
I am unable to mock my redisclient in email-service.spec.ts as per the dependency in the service file. I have tried useClass, added RedisService in provide and there are no get-redis methods.
I am able to mock the repositories and for services, I don't know for sure as I am stuck with redis.
Any idea how to mock redis, couldn't find anything in the docs. Also in the next step, will importing the services work or I have to do anything else?
ConfigService, HttpService, manageSavedSearchService: ManageSavedSearchService
If you want to mock the RedisClient in your tests you need to provide the same DI token as what you get back from #InjectRedisClient('0'). This will allow you to replace the redis client for the purposes of your test.
I'm not familiar with the specific Redis library you're using but assuming that it's this one you can take a look at how the token is constructed
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [EmailSubscriptionService,{
provide: EmailSubscriptionService,
useValue: {
getClient: jest.fn(),
}
}],
}).compile();
service = module.get<EmailSubscriptionService>(EmailSubscriptionService);
});
this seems to work somehow, the service class constructor uses many other classes, but using it in provide works..it is kind of counter intuitive as the classes in constructor need to be mocked individually, but without doing that it works.
I try to create unit tests for my small application. I want to test a service that uses injected configurations and other services.
#Injectable()
export class AuthService {
private readonly localUri: string;
constructor(
#Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
#Inject(PROVIDER_CONFIG_TOKEN) private readonly providerConfig: IProviderConfig,
private readonly _httpService: HttpService,
private readonly _usersService: UsersService,
) {
this.localUri = `http://${this.coreConfig.domain}:${this.coreConfig.port}`;
}
...
/**
* Checks if a given email is already taken
* #param email
*/
async isEmailTaken(email: string): Promise<boolean> {
return (await this._usersService.findUserByEmail(email)) !== undefined;
}
...
I do not understand how to test this service. I don't know how to provide a correct TestModule provider for the injected configuration #Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig
const testCoreConfig = '{...}'
const module = await Test.createTestingModule({
providers: [AuthService, {
provide: 'CORE_CONFIG_TOKEN',
useClass: testCoreConfig ,
}],
}).compile();
Also I am not sure if I would need also create the other imported services. I just would like to check if they are called. And if so return mock data. This I can do but I am stuck with the module setup.
All samples I found so far where just services with one repository. And more or less checks if the service exists. But no checks against the logic of the implementation and the connections between the classes.
I hope my question is clear
Thank you
I think you should be using the useValue property instead of useClass if you're passing an object of key values?
providers: [
{
provide: CORE_CONFIG_TOKEN,
useValue: {
domain: 'nestjs.com',
port: 80,
},
},
],
There's also some documentation on creating a config provider/service on nestjs for your modules.
I've also created a nestjs-config module you can use similar to this.
#Injectable()
class TestProvider {
constructor(private readonly #InjectConfig() config) {
this.localUri = config.get('local.uri');
}
#Module({
imports: [ConfigModule.load('path/to/config/file/*.ts')],
providers: [TestProvider],
})
export AppModule {}
//config file 'config/local.ts'
export default {
uri: `https://${process.env.DOMAIN}:/${process.env.PORT}`,
}
I have just started with Unit-Testing, and I have been able to mock my own services and some of Angular and Ionic as well, but no matter what I do ChangeDetectorRef stays the same.
I mean which kind of sorcery is this?
beforeEach(async(() =>
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [
Form, DomController, ToastController, AlertController,
PopoverController,
{provide: Platform, useClass: PlatformMock},
{
provide: NavParams,
useValue: new NavParams({data: new PageData().Data})
},
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock}
],
imports: [
FormsModule,
ReactiveFormsModule,
IonicModule
],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
],
viewProviders: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
]
}
})
.compileComponents()
.then(() => {
let fixture = TestBed.createComponent(MyComponent);
let cmp = fixture.debugElement.componentInstance;
let cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
})
));
it('fails no matter what', async(() => {
spyOn(cdRef, 'markForCheck');
spyOn(cmp.cdRef, 'markForCheck');
cmp.ngOnInit();
expect(cdRef.markForCheck).toHaveBeenCalled(); // fail, why ??
expect(cmp.cdRef.markForCheck).toHaveBeenCalled(); // success
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
}));
#Component({
...
})
export class MyComponent {
constructor(private cdRef: ChangeDetectorRef){}
ngOnInit() {
// do something
this.cdRef.markForCheck();
}
}
I have tried everything , async, fakeAsync, injector([ChangeDetectorRef], () => {}).
Nothing works.
Update 2020:
I wrote this originally in May 2017, it's a solution that worked great at the time and still works.
We can't configure the injection of a changeDetectorRef mock through the test bed, so this is what I am doing these days:
it('detects changes', () => {
// This is a unique instance here, brand new
const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef);
// So, I am spying directly on the prototype.
const detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');
component.someMethod(); // Which internally calls the detectChanges.
expect(detectChangesSpy).toHaveBeenCalled();
});
Then you don't care about private attributes or any.
In case anyone runs into this, this is one way that has worked well for me:
As you are injecting the ChangeDetectorRef instance in your constructor:
constructor(private cdRef: ChangeDetectorRef) { }
You have that cdRef as one of the private attributes on the component, which means you can spy on the component, stub that attribute and have it return whatever you want. Also, you can assert its calls and parameters, as needed.
In your spec file, call your TestBed without providing the ChangeDetectorRef as it won't provide what you give it. Set the component that same beforeEach block, so it is reset between specs as it is done in the docs here:
component = fixture.componentInstance;
Then in the tests, spy directly on the attribute
describe('someMethod()', () => {
it('calls detect changes', () => {
const spy = spyOn((component as any).cdRef, 'detectChanges');
component.someMethod();
expect(spy).toHaveBeenCalled();
});
});
With the spy you can use .and.returnValue() and have it return whatever you need.
Notice that (component as any) is used as cdRef is a private attribute. But private doesn't exist in the actual compiled javascript so it is accessible.
It is up to you if you want to access private attributes at runtime that way for your tests.
Not sure if this a new thing or not, but changeDetectorRef can be accessed via fixture.
See docs: https://angular.io/guide/testing#componentfixture-properties
We ran into the same issue with change detector mocking and this is ended up being the solution
Probably one point that needs to be pointed out, is that in essence here you want to test your own code, not unit test the change detector itself (which was tested by the Angular team).
In my opinion this is a good indicator that you should extract the call to the change detector to a local private method (private as it is something you don't want to unit test), e.g.
private detectChanges(): void {
this.cdRef.detectChanges();
}
Then, in your unit test, you will want to verify that your code actually called this function, and thus called the method from the ChangeDetectorRef. For example:
it('should call the change detector',
() => {
const spyCDR = spyOn((cmp as any).cdRef, 'detectChanges' as any);
cmp.ngOnInit();
expect(spyCDR).toHaveBeenCalled();
}
);
I had the exact same situation, and this was suggested to me as a general best practice for unit testing from a senior dev who told me that unit testing is actually forcing you by this pattern to structure your code better. With the proposed restructuring, you make sure your code is flexible to change, e.g. if Angular changes the way they provide us with change detection, then you will only have to adapt the detectChanges method.
For unit testing, if you are mocking ChangeDetectorRef just to satisfy dependency injection for a component to be creation, you can pass in any value.
For my case, I did this:
TestBed.configureTestingModule({
providers: [
FormBuilder,
MyComponent,
{ provide: ChangeDetectorRef, useValue: {} }
]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)
It will create myComponent successfully. Just make sure test execution path does not need ChangeDetectorRef. If you do, then replace useValue: {} with a proper mock object.
In my case, I just needed to test some form creation stuff using FormBuilder.
// component
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public someHandler() {
this.changeDetectorRef.detectChanges();
}
// spec
const changeDetectorRef = fixture.componentRef.changeDetectorRef;
jest.spyOn(changeDetectorRef, 'detectChanges');
fixture.detectChanges(); // <--- needed!!
component.someHandler();
expect(changeDetectorRef.detectChanges).toHaveBeenCalled();
How do I mock a service that depends on another service that is in a component?
Please check code below.
a.component.ts
#Component({
selector: 'my-comp',
templateUrl: './my.component.html',
providers: [ MyServiceA ]
})
export class MyComponent {
my-service-a.service.ts
#Injectable()
export class MyServiceA{
constructor(private myServiceB: MyServiceB) {}
my-service-b.service.ts
export class MyServiceB{
constructor(private myServiceC: MyServiceC,
private myServiceD: MyServiceD) {}
How do I mock the service in the a.component.spec.ts in the TestBed configuration? Please help. Thank you.
You can mock it however you want. The other services don't matter. I think maybe the problem you are facing is with the #Component.providers. Using this, any mocks you configure in the TestBed aren't used as the #Component.providers take precedence, causing Angular to try to create it, instead of using the mock.
To get around that, Angular offers the TestBed.overrideComponent method, so that we can override things like the template and providers of the #Component
beforeEach(() => {
let myMockService = new MyMockService();
TestBed.configureTestingModule({
providers: [
// the following will not be used
{ provide: MyService, useValue: myMockService }
]
});
TestBed.overrideComponent(MyComponent, {
set: {
providers: [
// this will override the #Component.providers:[MyService]
{ provide: MyService, useValue: myMockService }
]
}
});
})
I have a question in creating specs/unit tests in Angular2. Whenever you are injecting a mocked service, when do you use the inject function as the one below
it('function that the component calls',
inject([MyService], (service: MyService) => { // ...
}));
Or when do you use it as the one below
beforeEach(() => {
let myMockService = new MyMockService();
TestBed.configureTestingModule({
providers: [
{ provide: MyService, useValue: myMockService }
]
});
TestBed.overrideComponent(MyComponent, {
set: {
providers: [
{ provide: MyService, useValue: myMockService }
]
}
});
})
Can someone enlighten me on the matter?
The main reason to use it is to get access to the service in your test, when Angular is creating it. For instance
providers: [ MyService ]
Here Angular is creating it, and you only have access to it through Angular's injector.
But you're providing the service as a value then there is no need to use inject as you already have access to it
let serviceInstance = new Service();
provider: [ { provide: MyService, useValue: serviceInstance } ]
Here you already have access to serviceInstance so no need to get it from the injector.
Also if you don't need to access to the service inside the test, then there's not even a need to try and access it. But sometimes your mock will have thing you want to do with it inside the test.
Aside from inject, there are only ways to access the service
You could...
For your particular example you don't need inject at all. You just need to move the mock outside the scope of the beforeEach so that the it can use it
let myMockService = new MyMockService();
beforeEach(() => {
})
it('function that the component calls', () => {
myMockService.doSomething();
}));
You could...
Instead of using inject, you could get it from the TestBed, which acts like an injector. Maybe this is preferred as you can add it in your beforeEach
let service;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ MyService ]
});
service = TestBed.get(MyService);
})
it('function that the component calls', () => {
service.doSomething();
}));
You could...
Get it from the DebugElement which also acts like an injector
it('function that the component calls', () => {
let fixture = TestBed.createComponent(TestComponent);
let service = fixture.debugElement.get(MyService);
}));
So it's really a matter of preference. I personally try to stop using inject, as there are the other, less verbose options.
You use inject() when you want to get instances from the provide passed into your test code (for example the HTTP MockBackend) or any other service you want to communicate to directly in your test code.
TestBed.configureXxx only sets up providers for the test component.