'How can I test my service in Angular with Jasmine?

I created a service which looks like this:

@Injectable({
    providedIn: 'root'
})
export class NotificationService {

    private notifications: Notification[] = []
    private readonly notifications$: Subject<Notification[]> = new Subject()

    getNotifications(): Observable<Notification[]> {
        return this.notifications$
    }

    addNotification(config: Notification) {
        const newNotification = {id: uuidv4(), ...config}
        this.notifications.unshift(newNotification)
        this.notifications$.next(this.notifications)

        setTimeout(()=>{
            this.removeNotification(newNotification.id)
        }, 10000)
    }

    removeNotification(id: string): void {
        this.notifications = this.notifications.filter(notifcation => notifcation.id !== id)
        this.notifications$.next(this.notifications)
    }
}

This is used by the app component:

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {

    notifications: Notification[]

    constructor(notificationService: NotificationService) {
        notificationService.getNotifications().subscribe(notifications =>
            this.notifications = notifications
        )
    }
}

I would like to test this service using Jasmine. This is what I have in mind:

describe('Notification service', () => {
    let service: NotificationService

    beforeEach(() => {
        service = new NotificationService()
    })

    it('should have no notifications initially',
        () => {
            service.getNotifications().subscribe(value => {
                expect(value.length).toEqual(0)
            })
        }
    )

    it('should have one notification after adding a notification',
        () => {
            
            service.getNotifications().subscribe(value => {
                expect(value.length).toEqual(1)
            })

            service.addNotification({
                title: 'asdasdad',
                message: 'asdasdasd',
                type: 'info'
            })
        }
        )
})

it should have no notifications initially: this does not work because even if I subscribe to getNotifications() there is no value in the next function. how should I expect the notifications in the NotificationService to be an empty array?

it should have one notification after adding a notification: this one seems to be working and passing but I don't like that I first have to subscribe to getNotifications() and I am calling the addNotification() after that. It violates the Arrange, Act and Assert pattern. How could I write this test better?



Solution 1:[1]

I solved it this way:

I changed notifications$ to be a BehaviorSubject. This way the initial value is emitted in the moment of subscribing to it.

...
export class NotificationService {

    private notifications: NotificationConfig[] = []
    private readonly subject: BehaviorSubject<NotificationConfig[]>

    constructor() {
        this.subject = new BehaviorSubject(this.notifications)
    }

    getNotifications(): Observable<NotificationConfig[]> {
        return this.subject.asObservable()
    }
...

and the unit tests:

it('should have no notifications initially', () => {
    let actualValue: NotificationConfig[] | undefined
    service.getNotifications().subscribe(value => {
        actualValue = value
    }).unsubscribe()

    expect(actualValue).toEqual([])
})

it('should have one right notification after adding a notification', () => {
        const expectedNotification: NotificationConfig = {
            ...
        }
        service.addNotification(dummyNotification)

        let actualValue: NotificationConfig[] | undefined
        service.getNotifications().subscribe(value => {
            actualValue = value
        }).unsubscribe()

        expect(actualValue.length).toEqual(1)
        expect(actualValue[0]).toEqual(expectedNotification)
    }
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel Tahin