Angular - Unit testing Subject()? - javascript

I have a Angular service that simply uses a Subject, but I am unsure of how to write unit tests for it.
I saw [this thread][1], but I didn't find it terribly helpful.
I have attempted to mock next() but I am more than a little lost.

You should spy on service.serviceMsg and not service, because next() method appears to be on serviceMsg subject.
it('should catch what is emitted', () => {
const nextSpy = spyOn(service.serviceMsg, 'next');
service.confirm(ACTION);
expect(nextSpy).toHaveBeenCalled();
});
EDIT :
You should also change the way you are creating service instance. What you show in your code is applicable only for component instance creation
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MessageService]
});
service = TestBed.get(MessageService); // get service instance
httpMock = TestBed.get(HttpTestingController);
});

Firstly you can just subscribe to your Subject and inside expect some value and after that just execute method which will emit that:
it('should catch what is emitted', () => {
service.serviceMsg.subscribe(msg => {
expect(msg).toEqual(something);
});
service.confirm(value); // this should match what you expect above
});

Related

Angular Karma JUnit test - SpyOn does not work within private method

I try to test my component, I know the component works fine but my test gives error since Angular version has been updated to 12.
This is my component:
ngOnInit() {
if (versonA) {
this.doThis();
} else {
this.doThat();
}
}
private doThis() {
this.myService.confirm({
message: message,
accept: () => {
this.doAcceptLogic();
}, reject: () => {
console.log('reject')
this.doRejectLogic();
}
});
}
And this is my test:
beforeEach(async () => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.autoDetectChanges();
spyOn(TestBed.get(MyService), 'confirm').and.callFake((params: Confirmation) => {
params.reject();
});
await fixture.whenStable();
});
And still, this spyOn does not seem to work.
I put a lot of console logs into my code and the doThis() method still get called but the log within my confirm method ('reject') does not get written to the console.
I cannot see why.
As I change the doThis() method to public and call it directy from my test as component.doThis(), then it runs to into the mocked myService.
Can anybody explain my why?
Thanks a lot!
You can call private methods in your spec either by casting to any
(component as any).doThis();
or using []
component['doThis']();
You call fixture.autoDetectChanges() it may call ngOnInit which is calling doThis before even you created the spy.

Jasmine test case for PrimeNg Confirmation Service not working

I have a function which executes some operations in the "accept" call of PrimeNg Confirmation service. I tried to write a Unit test case for it as following:
fit('submit preview config', fakeAsync(() => {
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
const confirmationService = TestBed.get(ConfirmationService);
tick(200);
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
});
flush();
}));
The problem is the execution never goes inside callFake. The test case passes but the operation never takes place. Any ideas are welcome.
This is the function I want to test:
submitPreviewForm() {
const messageContent = `<strong>You have updated the following fields:</strong><br/>
<span class="subText" style="font-size: 12px; color: blue;">•${Array.from(this.updatedHighlightedFields).join('<br/> •')}</span>
<br/>
<strong>This will clear JIRA data from DB. Are you sure you want to proceed?</strong>`;
this.confirmationService.confirm({
message: messageContent,
accept: () => {
...
}
});
}
I am using V6 of PrimeNg.
I saw the implementation on this Stack Overflow question:
Angular Unit Test of a PRIME ng confirmation service
Your order of operations seems to be a bit off, you need to spy before calling submitPreviewform.
Try this:
fit('submit preview config', fakeAsync(() => {
const confirmationService = TestBed.get(ConfirmationService); // grab a handle of confirmationService
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
}); // spy on confirmationService.confirm now
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
tick(200);
flush();
}));

Subscribing to a change, but needing to see variable from where Service is used?

My code has been refactored and some extracted into a service that subscribes to functions. However, my original code had a call within the subscription that referenced a variable within the file, but now I'm not sure how to best reach it?
I am struggling with where to place the line:
this.select.reset('some string'); found within the subscribeToMessageService() function.
Original code
event.component.ts
select: FormControl;
#ViewChild('mySelect') mySelect: ElementRef;
subscribeToMessageService() {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});
}
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
// do something else
});
});
}
Refactored code
status.service.ts
subscribeToMessageService(): void {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
// This is where 'this.select.reset('some string');' would have gone
});
}
status.component.ts
select: FormControl;
#ViewChild('exceptionalSelect') selector: ElementRef;
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
this.onStatusChange(value);
});
});
}
Since you still want to subscribe to the original source messageService.serviceMsg your new StatusService needs to expose this observable to the injecting component (StatusComponent).
This can be done for example by creating a public observable in the StatusService (possibly by utilising rxjs Subject or angular EventEmitter) and triggering the emit in the subscription of messageService.serviceMsg.
Then your StatusComponent only needs to inject StatusService and do
this.statusService.serviceMsg // <-- might choose another name to make clear that this is passed on.
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});

jest support for mocking javascript 'classes'

I'm on a project that uses jest as the testing framework for a nodejs server. On one controller (called ControllerImTesting), it news up an instance of a helper class (we'll call ClassIWantToMock and uses it for the controller. I already have many tests written around ClassIWantToMock itself so naturally when I test ControllerImTesting, I just want to mock ClassIWantToMock out.
It's pretty simple, I've created another js file in __mocks__ that contains a dumb version of ClassIWantToMock. When I new it up in ControllerImTesting, I want it to use the dumb version in __mocks__.
I've tried lots of configurations at this point but my desperation move is to use setMock like:
jest.setMock('/path/to/real/class/I/want/to/mock', '/path/to/dumb/version') How could this fail?
But when running the test, I get TypeError: ClassIWantToMock is not a constructor.
I look at my dumb version of ClassIWantToMock and change the style from something like const ClassIWantToMock = (req) => { to class ClassIWantToMock {. I get the same error which kind of makes sense because using the es6 class style is just syntactic sugar.
Just to confirm, in my real controller, I write a line, console.log(ClassIWantToMock) above the line where it news up the instance. It indeed prints out the '/path/to/dumb/version'. It is trying to mock it but cannot.
Is this a limitation of Jest? Or am I simply not using it correctly? --> How should this be done?
UPDATE
./ClassIWantToMock.js
class ClassIWantToMock {
constructor(stuff) {
this.stuff = stuff
}
doStuff() {
console.log('some real stuff')
}
}
module.exports = ClassIWantToMock
./__mocks__/ClassIWantToMock.js
class ClassIWantToMock {
constructor(fakeStuff) {
this.fakeStuff = fakeStuff
}
doStuff() {
console.log('some fake stuff')
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.js
const ClassIWantToMock = require('./ClassIWantToMock')
class ControllerImTesting {
static aMethod(req, res, next) {
const helper = ClassIWantToMock('real stuff')
helper.doStuff()
return next()
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.spec.js
jest.setMock('./ClassIWantToMock', './__mocks__/ClassIWantToMock')
const ControllerImTesting = require('./ControllerImTesting')
describe('basic test', () => {
test('should work', () => {
return ControllerImTesting.aMethod({}, {}, () => {}).then(() => {
// expect console to display 'some fake stuff'
})
})
})

Jasmine: testing observables with events

I am trying to test an angular2 application. I have a login form, which uses an observable to send data to the backend:
doLogin() {
this.usersService.login(this.model)
.subscribe((data) => {
console.log("In observable: " + data.isSuccess);
if (!data.isSuccess) {
this.alerts.push({});
}
});
}
In tests I am adding a spy on the service function, which returns observable, so that component can work on it:
usersService.login.and.returnValue(Observable.of(
<LoginResponse>{
isSuccess: true
}));
When everything is ready, I dispatch an event on submit button, which triggers doLogin function in component:
submitButton.dispatchEvent(new Event("click"));
fixture.detectChanges();
It works correctly. Unfortunately, when I check if usersService.login has been called in the test:
expect(usersService.login).toHaveBeenCalled();
I get an error, because the observable didn't finish and login has not been called yet.
How should I make sure, I check my spy after observable has finished?
I don't know how you configure the service on the component but it works for me when I override providers of the component created from TestComponentBuilder.
Let's take a sample. I have a service that returns a list of string:
import {Observable} from 'rxjs/Rx';
export class MyService {
getDogs() {
return Observable.of([ 's1', 's2', ... ]);
}
}
A component uses this service to display a list asynchronously when clicking a button:
#Component({
selector: 'my-list',
providers: [MyService],
template: `
<ul><li *ngFor="#item of items">{{ item }}</li></ul>
<div id="test" (click)="test()">Test</div>
`
})
export class MyList implements OnInit {
items:Array<string>;
service:MyService;
constructor(private service:MyService) {
}
test() {
this.service.getDogs().subscribe(
(dogs) => {
this.items = dogs;
});
}
}
I want to test that when I click on the "Test" button, the test method of the component is called and the getDogs method of the service is indirectly called.
For this, I create a test that instantiate directly the service and load the component using TestComponentBuilder. In this case, I need to call the overrideProviders method on it before calling createAsync. This way, you will be able to provide your spied service to be notified of the call. Here is a sample:
let service:MyService = new MyService();
beforeEach(() => {
spyOn(service, 'getDogs').and.returnValue(Observable.of(
['dog1', 'dog2', 'dog3']));
});
it('should test get dogs', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders(MyList, [provide(MyService, { useValue: service })])
.createAsync(MyList).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
}));
Edit
Since the event is triggered asynchronously, you could consider to use fakeAsync. The latter allows you to completly control when asynchronous processing are handled and turn asynchronous things in to synchronous ones.
You could wrap your test processing into
fakeAsync((): void => {
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
For more details, you could have a look at this question:
Does fakeAsync guarantee promise completion after tick/flushMicroservice

Categories