dimanche 28 juillet 2019

How can I properly test my React Native OAuth wrapper component?

I have written a React Native "Auth Portal" component, that links with an existing OAuth portal and handles getting the auth-code from the redirect URI and the subsequent token exchange request. It seems to be working well, but clearly I need to test this assumption, so I am trying to write unit/functional tests. How can I properly do this?

I originally considered extracting the functions used in the two useEffects out into separate, isolated functions and taking, for example, the authCode as an argument instead of from state and mocking this input.

However, I believe a better strategy is to test the component as a whole and just mock the response to the axios post request, comparing that mock to what get's stored in the AsyncStorage, as well as mocking a bad request/response to test the error handling.

Is this a good approach?

import axios from 'axios'
import AsyncStorage from '@react-native-community/async-storage'
import React, { useEffect, useState } from 'react'
import { Linking } from 'react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import { LoadingIndicator } from '../LoadingIndicator'

interface AuthPortalProps {
    client_id: string
    scopes: string[]
    client_secret: string
    redirect_uri: string
    onAuthComplete: () => void
    onError: () => void
}

interface ApiDataResponse {
    token_type: string
    expires_in: number
    access_token: string
    refresh_token: string
}

export const AuthPortal = ({
    client_id,
    scopes,
    client_secret,
    redirect_uri,
    onAuthComplete,
    onError,
}: AuthPortalProps) => {
    const [authCode, setAuthCode] = useState()

    const getAuthCodeFromRedirectUri = async (url: string) => {
        if (url.includes('code=')) {
            const regex = /[^=]+$/g
            const code = url.match(regex)!.toString()

            await setAuthCode(code)
        }
    }

    useEffect(() => {
        const getAuthCode = async () => {
            const url = `https://example.com/auth/?response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${scopes}`

            if (!authCode) {
                try {
                    InAppBrowser.openAuth(url, redirect_uri).then(response => {
                        if (response.type === 'success' && response.url && response.url.includes('code=')) {
                            getAuthCodeFromRedirectUri(response.url)
                            Linking.openURL(redirect_uri)
                        }
                    })
                } catch (error) {
                    console.log('Error: ', error.message)
                    onError()
                }
            }
        }

        getAuthCode()
        return () => {
            InAppBrowser.closeAuth()
        }
    }, [authCode, client_id, onError, redirect_uri, scopes])

    useEffect(() => {
        const getAuthRefreshToken = async () => {
            if (authCode) {
                try {
                    const { data }: { data: ApiDataResponse } = await axios.post(
                        'https://example.com/auth',
                        {
                            grant_type: 'authorization_code',
                            client_id: `${client_id}`,
                            code: `${authCode}`,
                            client_secret: `${client_secret}`,
                            redirect_uri: `${redirect_uri}`,
                        }
                    )

                    await Promise.all([
                        AsyncStorage.setItem('access_token', data.access_token),
                        AsyncStorage.setItem('refresh_token', data.refresh_token),
                    ])
                    setTimeout(() => {
                        onAuthComplete()
                    }, 1000)
                } catch (error) {
                    if (error.response) {
                        console.log('Error: ', error.response)
                    } else if (error.request) {
                        console.log('Error: ', error.request)
                    } else {
                        console.log('Error: ', error.message)
                    }
                    onError()
                }
            }
        }

        getAuthRefreshToken()
    }, [authCode, client_id, client_secret, onAuthComplete, onError, redirect_uri])

    return <LoadingIndicator />
}

Aucun commentaire:

Enregistrer un commentaire