I'm trying to log events and set user id/properties in Google Analytics for my angular 13 application working with #angular/fire 7.3.0.
In my Google Analytics dashboard, I can see the my event just fine, but nothing regarding User ID or User Properties.
Here is my app.module.ts :
import { AngularFireAnalyticsModule, ScreenTrackingService, UserTrackingService, CONFIG } from '#angular/fire/compat/analytics';
#NgModule({
declarations: [AppComponent],
imports: [
AngularFireModule.initializeApp(conf),
AngularFireAnalyticsModule,
// ...
],
providers: [
ScreenTrackingService,
UserTrackingService,
{ provide: CONFIG, useValue: { send_page_view: false, allow_ad_personalization_signals: false, anonymize_ip: true} },
// ...
],
bootstrap: [AppComponent]
})
export class AppModule { }
Here is the code I use to report Log and to set property
testID(){
this.analytics.setUserId("test_id");
}
testProperties(value: string) {
this.analytics.setUserProperties({ property: "property_test_value" });
}
testLog(){
this.analytics.logEvent("event_test");
}
Any idea on why I cannot see my properties / user ID ?
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'm totally new to TDD and I'm trying to debug a big Angular 5 application we're working in, at the company.
The app is working well, but now I have to implement tests, and I'm learning this stuff while I create the most basic and starter ones. I wrote this stuff already for the main module, just for trying this tooling:
describe('AppComponent', () => {
let httpClientSpy: { get: jasmine.Spy }
let dataReq: DataRequester;
let queryBuilder: PanelQueryBuilder;
let query: DataQuery;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MainComponent,
Menu,
MessageViewer
],
imports: [
BrowserModule,
BrowserAnimationsModule,
routing,
AngularFontAwesomeModule,
FormsModule,
HttpModule,
ChartModule,
Ng4LoadingSpinnerModule.forRoot(),
NgbModule.forRoot()
],
providers: [
ReactiveService,
DataRequester,
{ provide: APP_BASE_HREF, useValue : '/' }
]
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(MainComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it('userType should be defined', async(()=>{
expect(MainComponent.userType).toBeDefined();
}))
it('DataRequester exists and retrieves info', async(()=>{
beforeEach(() => {
// TODO: spy on other methods too
httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
dataReq = new DataRequester(<any> httpClientSpy);
queryBuilder = new PanelQueryBuilder();
});
expect(MainComponent).toBeDefined();
}))
it('1. Build query and check integrity', async()=>{
query = queryBuilder.buildInitialQuery("panel", "conversions", 144);
expect(query).toBeDefined();
})
it('2. Send query and check result', async()=>{
dataReq.requestData(query, 'conversions').then( res => {
expect(res).toContain("panel");
})
})
});
I want you to focus in one part: the DataRequester service. It's a service that has a method which returns a promise, and calls a specific part of our backend, returning the data. I just want here to check if this response object contains the property "panel", and the test...
...¡actually says it exists! But if I try to change the name of the property, to some non-existing property... it validates as true too. So maybe, the HTTP request is NOT working properly here, doing something wrong here.
Am I doing something bad in this code? Why doesn't the DataRequester "requestData" method execute properly, so Jasmine is able to properly test the conditions I want, in the response object?
Yes, you"re doign something bad in your code. But don't worry, I did the same thing when I started.
First, you must understand the basics of unit testing : unit tests are made to prevent side effects in a unit.
Side effects are changes in a wanted behavior : for instance, you want to color a div in blue, and after some code editing, it colors red : this is a side effect.
A unit is the feature you're testing. In Angular, you can see which one it is with this :
describe('AppComponent'
Here, you're testing AppComponent.
Now that we settled this, let's go over what is wrong in your tests : you use real instances of your services. This means that you don't don't a single unit anymore : you're testing several units.
To correct that
You have to mock your services. You will check if your component is actually calling the service, and not if the service is calling your API (this will be checked by the unit test of the service itself).
In your test bed :
TestBed.configureTestingModule({
declarations: [
MainComponent,
Menu,
MessageViewer
],
imports: [
BrowserModule,
BrowserAnimationsModule,
routing,
AngularFontAwesomeModule,
FormsModule,
HttpModule,
ChartModule,
Ng4LoadingSpinnerModule.forRoot(),
NgbModule.forRoot()
],
providers: [
{
provide: ReactiveService,
useValue : {}
},
{
provide: DataRequester,
useValue: {}
},
{ provide: APP_BASE_HREF, useValue : '/' }
]
Usually, components handle only the view : you don't really to mock them (although you should).
This allows you to remove the HttpModule, which isn't required in any test.
You can also remove your routing module, because Angular provides a mock of it already : RouterTestingModule.
Your test bed becomes this
TestBed.configureTestingModule({
declarations: [
MainComponent,
Menu,
MessageViewer
],
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterTestingModule,
AngularFontAwesomeModule,
FormsModule,
ChartModule,
Ng4LoadingSpinnerModule.forRoot(),
NgbModule.forRoot()
],
providers: [
{
provide: ReactiveService,
useValue : {}
},
{
provide: DataRequester,
useValue: {}
},
{ provide: APP_BASE_HREF, useValue : '/' }
]
Now you have a proper test bed.
All you have to do left, is add in the useValue of your mocks, every service property used by your component with the correct signature.
For instance, imagine your app component has this :
ngOnInit() {
this.dataRequester.requestWidth('URL').subscribe(res => this.reactiveService.width = res);
}
Then your test bed becomes this :
TestBed.configureTestingModule({
declarations: [
MainComponent,
Menu,
MessageViewer
],
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterTestingModule,
AngularFontAwesomeModule,
FormsModule,
ChartModule,
Ng4LoadingSpinnerModule.forRoot(),
NgbModule.forRoot()
],
providers: [
{
provide: ReactiveService,
useValue : {
width: 0
}
},
{
provide: DataRequester,
useValue: {
requestWidth: () => of(100)
}
},
{ provide: APP_BASE_HREF, useValue : '/' }
]
(Values of the mock aren't important, you'll change them on demand)
As you can see, because your requester service returns an Observable, you are forced to return one too. And because your reactive service stores the width, you have to declare a variable of type number.
Now, in your test, using the previous example, you will do this :
it(`should get width from requester and store it in reactive service`, fakeAsync(() => {
spyOn(component['dataRequester'], 'requestWidth').and.callThrough();
component.ngOnInit();
tick();
expect(component['dataRequester'].requestWidth).toHaveBeenCalledWith('URL');
expect(component['reactiveService'].width).toEqual(100);
}));
You declare what you do (test driven), you spy on your service (to see if it has been called), then you call through (because our mock is already an Observable and returns 100).
Then, you call the method to test, and flush the async calls out (fakeAsync and tick are part of Angular, you can find them in the docs about testing).
Finally, you make your expectations.
And with that, you successfully tested your first method !
I have a requirement, based on configuration parameters, pick a specific service as an injectable to the component. All the examples I see, really use a specific service and use it at component construction time.
Is there a way to perform this injection at run time?
You can use a factory that returns a different instance depending on some configuration
#NgModule({
providers: [
ConfigService,
{ provide: APP_BASE_HREF, useFactory: loadConfig, deps: [ConfigService] },
],
...
see also How to pass parameters rendered from backend to angular2 bootstrap method
function myServiceFactory(config:ConfigService) {
switch(config.someProp) {
case 'someValue': return new ClassA();
default: return new ClassB();
}
}
#Component({
providers: [
{ provide: MyService, useFactory: myServiceFactory, deps: [ConfigService] }
],
...
I've 3 applications which share a common framework so there's currently a bunch of duplicated code (basic ui layout, common code such as loggers etc, security/keycloak auth wrapper). I've extracted all the duplicate module code but also want to simplify the app module declaration itself. I've created an "Application" class containing a static method which takes a couple of params and builds an NgModule definition based on common modules, concatenating the passed in ones...
public static Define({modules, states, defaultState}) {
return {
imports: [
BrowserModule,
FormsModule,
HttpModule,
TreeModule,
NgbModule.forRoot(),
ToastModule.forRoot(<ToastOptions>{animate: 'flyRight', positionClass: 'toast-bottom-right'}),
UIRouterModule.forRoot(<RootModule>{ states: states.concat({name: 'app', url: '/app', component: ShellComponent}), useHash: true, otherwise: defaultState }),
CommonModule,
DialogsModule,
LayoutModule,
].concat(modules),
providers: [
{
provide: Http,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new AuthHttpService(backend, defaultOptions),
deps: [XHRBackend, RequestOptions]
}
],
bootstrap: [ UIView ]
};
}
Each application can then simply call this and just list its specific modules, states and an initial/default state like this...
#NgModule(
Application.Define({
modules: [
PatientIdentityModule,
RecordViewerModule,
ResourcesModule,
],
states: [
{name: 'app.resourceList', url: '/resourceList', component: ResourcesComponent },
{name: 'app.resourceEdit', url: '/resourceEdit/:itemAction/:itemUuid', component: ResourcesComponent },
{name: 'app.patientIdentity', url: '/patientIdentity', component : PatientIdentityComponent},
{name : 'app.recordViewer', url: '/recordViewer', component : RecordViewerComponent }
],
defaultState : { state: 'app.recordViewer', params: {} }
})
)
export class AppModule {}
This works great when all the code is together but when I try extracting the Application class and building a node module library, I then get
Error: No NgModule metadata found for 'AppModule'
Any ideas or am I going about this completely the wrong way!?
Angular 2.1.0, typescript 2.0.6, webpack 1.13.2