4

I've got a typescript module that uses the browser's native Blob. I'd like to test it in node so I need that same module to see a global fake Blob implementation. A very simple fake blob implementation will do for me

class Blob {

  parts?: any;
  options?: any;

  constructor(parts: any, options?: any) {
    this.parts = parts;
    this.options = options;
  }

  getType():string {
    return options.type;  // I know, hacky by just a demo
  }
}

But how do I inject it into the global namespace of node so that my code that normally runs in the browser will see it when I run the tests in node?

In other words imagine I have a class that's expected to return a native browser Blob

export class BlobMaker {
  static makeTextBlob(text):Blob {
    return new Blob([text], {type: 'text/text'});
  }
}

And now I want to test it in node (not in the browser) as in

import { BlobMaker } from '../src/blob-maker';
import * as expect from 'expect';

describe('BlobMaker', () =>  {
  it('makes a text blob', () => {
    const blob = BlobMaker.makeTextBlob('foo');

    expect(blob.getType()).equalTo('text/text');
  });
});

This fails to compile because Blob doesn't exist in node.

Apparently I can claim it exists by adding?

export interface Global extends NodeJS.Global {
  Blob: Blob;
}

But I still need to inject my fake Blob class above so that the code I'm testing will use it. Is there a way to do that or am I supposed to solve this some other way?

It doesn't seem like I can abstract Blob into some kind of interface since I need the signature of makeTextBlob to be an actual native browser Blob and not some custom type.

I guess I get that I could pass a Blob factory deep into my library from the tests. Passing a Blob factory that deep in seems like overkill. I actually tried this by typescript complained. I think because it thinks the type being returned by BlobMaker.makeBlob is different

Here's that code

let makeBlob = function(...args):Blob {
   return new Blob(...args);
};

export function setMakeBlob(fn):void {
   makeBlob = fn;
};

export class BlobMaker {
  static makeTextBlob(text):Blob {
    return makeBlob([text], {type: 'text/text'});
  }
}

Then in the tests

import { BlobMaker, setMakeBlob } from '../src/blob-maker';
import { Blob } from "./fake-blob';
import * as expect from 'expect';

describe('BlobMaker', () =>  {
  it('makes a text blob', () => {
    setMakeBlob(function(parts, options) {
      return new Blob();
    });

    const blob = BlobMaker.makeTextBlob('foo');

    expect(blob.getType()).equalTo('text/text');   // ERROR!
  });
});

I get an error there is no getType method on Blob. I'm guessing TS thinks the Blob returned by BlobMaker.makeTextBlob is the native one. I tried casting it

    const blob = BlobMaker.makeTextBlob('foo') as Blob;

But it didn't like that either.

Effectively this seems like it would all be solved if I could just inject my Blob class into the global namespace in node. So, how do I do that?

6
  • I was under the impression that /// <referenceing the .d.ts file from your BlobMaker module would be sufficient, have you tried that? Commented Jul 12, 2018 at 4:42
  • Sorry this is my first typescript experience but how would that help BlobMaker find Blob? Commented Jul 12, 2018 at 4:49
  • Oh wait I misread, your Blob is a class, not an interface (which should mean it's in a normal .ts file. Your BlobMaker module should import Blob from './blob' or wherever it's exported from. Even though it's understood to be a global variable, it's much easier to deal with it as part of a module when using TypeScript. Commented Jul 12, 2018 at 4:56
  • I'm not sure how that helps. This code still needs to run in the browser and return a browser native Blob (updated the question). I just want to test it with a fake blob in tests running in node. Commented Jul 12, 2018 at 5:17
  • I'm not particularly well-versed in TypeScript yet myself, but looking at the context here, I think you should look into mocking the Blob module. I found this answer which may or may not be useful to you, but what you can do is rewrite the file that Blob is in to attach to global only if nothing is already defined on global.Blob. Commented Jul 12, 2018 at 5:58

1 Answer 1

1

So this is how I solved it. Hope it's not too horrible

First according to this Q&A I can extend the global object in node by adding a separate file. In my case I made test.d.ts and put this inside

declare namespace NodeJS {
    interface Global {
        Blob: any
    }
}

Then in my test I did this

import { BlobMaker } from '../src/blob-maker';
import * as expect from 'expect';

class Blob {

  parts?: any;
  options?: any;

  constructor(parts: any, options?: any) {
    this.parts = parts;
    this.options = options;
  }

  getType():string {
    return options.type;  // I know, hacky by just a demo
  }
}

describe('BlobMaker', () =>  {
  it('makes a text blob', () => {

    global.Blob = Blob;

    const blob = BlobMaker.makeTextBlob('foo');

    expect(blob.getType()).equalTo('text/text');
  });
});

and it's working. I should probably move the part that's patching global.Blob to another module but it at least suggests a solution.

Sign up to request clarification or add additional context in comments.

1 Comment

That works but just to make sure it doesn't potentially interfere with other tests, I'd be more explicit about the mocking by rewriting describe('BlobMaker', () => { const { Blob } = global; beforeEach(() => { global.Blob = require('./blob'); }); afterEach(() => { global.Blob = Blob; }); it(...); });

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.