Skip to content

Mock for unit tests #94

Open
Open
@maximilize

Description

@maximilize

Sometimes it's helpful to test code in real world scenarios, for this a working download function is required. To help others, I want to share my work on this. Create a file like __mocks__/react-native-blob-util.js:

import fetch from 'cross-fetch';
import fs from 'fs';
import fsPromises from 'fs/promises';
import fsPath from 'path';
import stream from 'stream';

const ensureDir = p => {
  if (!fs.existsSync(p)) {
    try {
      fs.mkdirSync(p);
    } catch (error) {
      if (!fs.existsSync(tempPath)) {
        throw error;
      }
    }
  }
  return p;
};

const deleteFolderRecursive = path => {
  if (fs.existsSync(path)) {
    if (fs.lstatSync(path).isDirectory()) {
      fs.readdirSync(path).forEach(file => {
        deleteFolderRecursive(fsPath.join(path, file));
      });
      fs.rmdirSync(path);
    } else {
      fs.unlinkSync(path);
    }
  }
};

deleteFolderRecursive(`/tmp/react-native-fs-mocks/${Date.now()}`);
const tempPath = ensureDir('/tmp/react-native-fs-mocks');
const cachePath = ensureDir(`${tempPath}/cache`);
const homePath = ensureDir(`${tempPath}/home`);

export default {
  fs: {
    exists: fs.existsSync,
    unlink: deleteFolderRecursive,
    readdir: fsPromises.readdir,
    mkdir: fsPromises.mkdir,
    appendFile: fsPromises.appendFile,
    writeFile: fsPromises.writeFile,
    readFile: fsPromises.readFile,
    mv: fsPromises.mv,
    stat: fsPromises.stat,
    dirs: {
      DocumentDir: homePath,
      CacheDir: cachePath,
      DownloadDir: fsPath.join(homePath, 'downloads'),
    },
    downloadFile: ({fromUrl, toFile, headers}) => ({
      promise: (async () => {
        const res = await fetch(fromUrl, {headers});
        const data = {
          statusCode: res.status,
          url: res.url || fromUrl,
          contentLength: res.headers['content-length'],
          ...res,
        };
        await new Promise((resolve, reject) =>
          stream.pipeline(res.body, fs.createWriteStream(toFile), err =>
            err ? reject(err) : resolve(),
          ),
        );
        return data;
      })(),
    }),
  },
  config: ({path, overwrite = true}) => {
    let resolve;
    let reject;
    const task = new Promise((a, b) => {
      resolve = a;
      reject = b;
    });

    let onProgress;
    let cancel = false;

    task.fetch = (method, url, headers) => {
      fetch(url, {method, headers})
        .then(async res => {
          const resHeaders = {};
          for (const key of res.headers.keys()) {
            resHeaders[key] = res.headers.get(key);
          }
          const info = {
            ...res,
            statusCode: res.status,
            status: res.status,
            url: res.url || url,
            headers: resHeaders,
            contentLength: res.headers.get('content-length'),
          };
          let received = 0;
          res.body.on('data', chunk => {
            if (cancel) {
              throw new Error('cancel');
            }
            received += chunk.length;
            if (onProgress) {
              onProgress(received, info.contentLength);
            }
          });
          stream.pipeline(
            res.body,
            fs.createWriteStream(path, {flags: overwrite ? '' : 'a'}),
            error => {
              if (error) {
                reject(error);
              } else {
                resolve({info: () => info});
              }
            },
          );
        })
        .catch(reject);

      return task;
    };

    task.progress = (options, fn) => {
      onProgress = fn;
      return task;
    };

    task.cancel = () => {
      cancel = true;
    };

    return task;
  },
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions