06.10.2022

Playing with Marbles - Elegantes Testen von RxJS

von Marco Sieben

Graphik Playing with Marbles - Elegantes Testen von RxJS
import { Observable } from 'rxjs' 
 
export type Product = { 
    code: string 
    description: string 
} 
 
export type ProductAdapter = { 
    fetchProductDetails: (productCode: string, language: string) => Observable<Product>
}
import { combineLatest, Observable, switchMap } from 'rxjs'
import { Product, ProductAdapter } from './adapter'

export class ProductDetailService {
    constructor(
        private currentProductCode$: Observable<string>,
        private activeLanguage$: Observable<string>,
        private productAdapter: ProductAdapter,
    ) {
    }

    getProductDetails(): Observable<Product> {
        return combineLatest([
            this.currentProductCode$,
            this.activeLanguage$,
        ]).pipe(
            switchMap(([productCode, language]) => this.productAdapter.fetchProductDetails(productCode, language)),
        )
    }
}
import { ProductDetailService } from './product-detail.service'

describe('ProductDetailService', () => {
    describe('getProductDetails', () => {
        let serviceUnderTest: ProductDetailService

        beforeEach(() => {
            serviceUnderTest = new ProductDetailService(___, ___, ___)
        })
    })
})
beforeEach(() => {
    const mockActiveLanguage$ = hot('    ---d-------e--f-------', {
        d: 'de',
        e: 'en',
        f: 'fr',
    })

    const mockCurrentProductCode$ = hot('-----1-------------2--', {
        1: '123',
        2: '456',
    })

    serviceUnderTest = new ProductDetailService(mockCurrentProductCode$, mockActiveLanguage$, ___)
})
const mockProductAdapter: ProductAdapter = {
    fetchProductDetails: (productCode: string, language: string) => cold('---(X|)', {
        X: {code: productCode, description: `description in language ${language}`},
    }),
}
it('fetches product details each time language or code change', () => {
    // ---d-------e--f-------     - language
    // -----1-------------2--     - productCode
    //      ---X                  │
    //            ---X            ├ fetch (completion ignored for simplicity)
    //               ---X         │
    //                    ---X    │

    expect(serviceUnderTest.getProductDetails()).toBeObservable(
        cold('--------1--------2----3', {
            1: {code: '123', description: 'description in language de'},
            2: {code: '123', description: 'description in language fr'},
            3: {code: '456', description: 'description in language fr'},
        })
    )
})
import { ProductDetailService } from './product-detail.service'
import { cold, hot } from 'jest-marbles'
import { ProductAdapter } from './adapter'

describe('ProductDetailService', () => {
    describe('getProductDetails', () => {
        let serviceUnderTest: ProductDetailService

        beforeEach(() => {
            const mockActiveLanguage$ = hot('    ---d-------e--f-------', {
                d: 'de',
                e: 'en',
                f: 'fr',
            })

            const mockCurrentProductCode$ = hot('-----1-------------2--', {
                1: '123',
                2: '456',
            })

            const mockProductAdapter: ProductAdapter = {
                fetchProductDetails: (productCode: string, language: 
string) => cold('---(X|)', {
                    X: {code: productCode, description: `description in language ${language}`},
                }),
            }

            serviceUnderTest = new ProductDetailService(mockCurrentProductCode$, mockActiveLanguage$, mockProductAdapter)
        })

        it('fetches product details each time language or code change', () => {
            // ---d-------e--f-------     - language
            // -----1-------------2--     - productCode
            //      ---X                  │
            //            ---X            ├ fetch (completion ignored for simplicity)
            //               ---X         │
            //                    ---X    │

            expect(serviceUnderTest.getProductDetails()).toBeObservable(
                cold('--------1--------2----3', {
                    1: {code: '123', description: 'description in language de'},
                    2: {code: '123', description: 'description in language fr'},
                    3: {code: '456', description: 'description in language fr'},
                })
            )
        })
    })
})

Beitrag von

Marco Sieben
Marco Sieben

Marco freut sich als agiler Software-Entwickler immer, wenn er auf neue, spannende Technologien trifft. Wenn er dann noch die richtigen Mittel an die Hand bekommt, um damit das “Was” und nicht das “Wie” testen zu können, ist er wunschlos glücklich.

Weitere Blog-Beiträge

Loading...