Jest: Test if object, array or string.

Created on 3 May 2017  ·  29Comments  ·  Source: facebook/jest

Is there a way to check if a component is an object, array or string? This would be similar to chai's 'should.be.a' Ex.: validationResult.SSN[0].should.be.a('string').

Most helpful comment

A simple toBeType extension for those who want it

expect.extend({
    toBeType(received, argument) {
        const initialType = typeof received;
        const type = initialType === "object" ? Array.isArray(received) ? "array" : initialType : initialType;
        return type === argument ? {
            message: () => `expected ${received} to be type ${argument}`,
            pass: true
        } : {
            message: () => `expected ${received} to be type ${argument}`,
            pass: false
        };
    }
});

describe("testing extended expect", () => {
    it("tests normal types correctly", () => {
        expect("").toBeType("string");
        expect({}).toBeType("object");
        expect(1).toBeType("number");
    });
    it("tests array types correctly", () => {
        expect([]).toBeType("array");
    });
    it("works with promises", () => {
        expect(Promise.resolve([])).resolves.toBeType("array");
    });
});

Pretty simple to implement. Really should be in core tbh.

Note - if you're putting that extend in your setup files then you want it in setupTestFrameworkScriptFile NOT setupFiles (as extend is available only in the former)

All 29 comments

No, there isn't. You'll find the list of all available matchers here: https://facebook.github.io/jest/docs/en/expect.html

You can also use plain JavaScript or helper library like lodash for that:

test('name', () => {
  // array
  expect(Array.isArray(['value'])).toBe(true);
  // string
  expect(typeof 'value').toBe('string');
  // object
  expect({value: 'value'}).toBeTruthy();
  expect(typeof {value: 'value'}).toBe('object');
})

Minor point - this doesn't help with promise results.

expect(somePromise).resolves.toBe(...) at this point there is no way to check type. If you don't care what the contents are but just that it is a string. I hoped expects.stringContaining("") to be a work around but that doesn't work either.

@abritinthebay I am in exact that situation and this is the first result in Google, maybe this should be reopened?

Certainly it should be thought about a bit more. My workaround was to add to the chain so that does the typeof part. eg:

expect(somePromise.then(data => typeof data)).resolves.toBe("object");

it works, but it's not exactly clean.

@thymikee Checking types of things is a common enough use case (universal) that there isn't really any excuse for a testing framework that lacks them. Your alternatives are unacceptable as we lose all context of what we are testing.

This expect(Array.isArray(['value'])).toBe(false); fails with

expect(received).toBe(expected)
    Expected value to be (using ===):
      false
    Received:
      true. 

So we either get terrible assertion messages or we have to extend Jest to support these sorts of checks. Doesn't it make more sense for the maintainers of Jest to do this once as opposed to every person that uses requires these features implementing them on their own?

Create your own matchers with expect.extend then and publish as an npm module. If it gets popular, we may merge it to Jest core eventually ;)

A simple toBeType extension for those who want it

expect.extend({
    toBeType(received, argument) {
        const initialType = typeof received;
        const type = initialType === "object" ? Array.isArray(received) ? "array" : initialType : initialType;
        return type === argument ? {
            message: () => `expected ${received} to be type ${argument}`,
            pass: true
        } : {
            message: () => `expected ${received} to be type ${argument}`,
            pass: false
        };
    }
});

describe("testing extended expect", () => {
    it("tests normal types correctly", () => {
        expect("").toBeType("string");
        expect({}).toBeType("object");
        expect(1).toBeType("number");
    });
    it("tests array types correctly", () => {
        expect([]).toBeType("array");
    });
    it("works with promises", () => {
        expect(Promise.resolve([])).resolves.toBeType("array");
    });
});

Pretty simple to implement. Really should be in core tbh.

Note - if you're putting that extend in your setup files then you want it in setupTestFrameworkScriptFile NOT setupFiles (as extend is available only in the former)

Thanks @abritinthebay

So I wrapped that up in an npm module if people want it:

https://www.npmjs.com/package/jest-tobetype

describe("assertion framework", ()=> {
 it("should check primitive types", () => {
   expect(expect.toBeA).toBeA("function")
  })
})

Failed: expect(...).toBeA is not a function
TypeError: expect(...).toBeA is not a function

https://github.com/jest-community/jest-extended has all the type matchers you could want (I think).

I've been using toBeInstanceOf in my tests:

expect($wrapper.vm.countries).toBeInstanceOf(Array);

Create your own matchers with expect.extend then and publish as an npm module. If it gets popular, we may merge it to Jest core eventually ;)

Yeah, and might write your own jest framework, while you're at it.

This one just might top the worst answers you can get on GitHub.

So @abritinthebay did exactly what was requested by @thymikee (which was far more than the standard pull request).

Now that that brave soul did all the work, when can the rest of us finally get this matcher (without having to install yet another library)? Are the maintainers still pushing the idea that this doesn't belong in Jest, or did this just fall off their radar?

We are pretty strict with what makes it into core and don't typically add sugar matchers. Jest core is a fairly large architecture and every matcher we add increases the maintenance cost

For sugar, we generally recommend https://github.com/jest-community/jest-extended

One man's sugar is another man's (or in this case, at least seven other people's) really useful and logical feature that belongs in the core library.

Obviously as a maintainer your vote trumps all of ours, and you have all sorts of concerns we don't so I fully respect that. But I'd simply ask that you look at why everyone here considers this feature to belong in the core library (so strongly that one person jumped through multiple hoops to write the code for you). There's a need here, and if you ignore it Jest core library users (and let's be honest, 90% of them will never even hear about jest-extended) will lose out.

.to.be.an.instanceOf is not going to be how many users think to check types, so for those users, even if you see it as sugar, you are effectively denying them the ability to check types in Jest without an additional library.

Yeah I hear ya. To be clear, by "sugar" I meant syntax that is designed to make things easier to read or express. Sugar, by definition, is a variation of a feature that already exists

In this case, we have:

// Supported
expect(typeof foo).toBe('string');

// Proposed Sugar
expect(foo).toBeType('string');

So it's not that we don't support checking types. We do. We support the first option. This option uses the core toBe matcher which we have spent a lot of time fixing the bugs in, and tweaking the message for, so that users have a good experience

There are nearly 60 matchers in jest-extended and many of those are pure sugar. For any of those matchers you could probably find at least 7 other people who find them really useful, so if that was the heuristic we used for adding to core we would probably spend all of our time just maintaining matchers

To be completely fair - most matchers are "sugar" at some level. I mean toBeGreaterThanOrEqual is just sugar for expect(foo >= bar).toBe(true);

Matchers are really _almost all_ sugar around boolean statements ;)

(I say this not to dig, just to point out that it's... a very blurred line)

As abritinthebay suggested, it's not really about sugar, it's about "necessary" and "unnecessary" (for the core library) sugar. You've got a bunch of people in this thread saying "hey, being able to check all types is something that should be in the core of a testing library" (ie. it is necessary).

Listen to us or don't, again as maintainer you have lots of other concerns. But I don't think the right response is to come say "your's is just inherently unnecessary sugar" (that's me trying to paraphrase you, not trying to put words in your mouth) when it's not inherent: it's 100% your call whether Jest can check all types or not out of the box.

what about, isn't that hard :P?

expect(Array.isArray(['your', 'array'])).toBe(true);

expect(typeof something === "object").toBe(true); 
// - or -
expect(something instanceof Object).toBe(true);

expect(typeof something === "string").toBe(true); 

@nahumzs While it works, the problem is that on your test output when failing, it will say ‘expected false to be true’, which isn’t very helpful ;)

I think this is the way to go :)

describe('type check', () => {
    test('should be type string', () => {
        expect(typeof '').toBe('string')
    })

    test('should be type number', () => {
        expect(typeof 10).toBe('number')
    })

    test('should be type boolean', () => {
        expect(typeof true).toBe('boolean')
    })

    test('should be type undefined', () => {
        expect(typeof undefined).toBe('undefined')
    })

    test('should be type object', () => {
        expect(typeof { foo: 'bar' }).toBe('object')
    })

    test('should be type function', () => {
        expect(typeof function() {}).toBe('function')
    })

    test('should be type null', () => {
        expect(typeof null).toBe('object')
    })
})

I refactored the implementation provided by @abritinthebay. It seems for me a little bit comfortable to work with.

```javascript
expect.extend({
/*
* @param {
} received
* @param {string|string[]} arg
* @return {{pass:boolean,message:(function():string)}}
*/
toBeType(received, arg) {
const isCorrectType = arg => {
const receivedType = typeof received;

        const checkForSingle = arg => {
            const type = receivedType === 'object'
                ? Array.isArray(received)
                    ? 'array'
                    : receivedType
                : receivedType;

            return type === arg;
        };

        const checkForArr = arg => {
            const reducer = (prev, curr) => prev || isCorrectType(curr).isCorrect;

            return arg.reduce(reducer, false);
        };

        return {
            receivedType,
            isCorrect: Array.isArray(arg)
                ? checkForArr(arg)
                : checkForSingle(arg)
        };
    };

    const {isCorrect, receivedType} = isCorrectType(arg);

    return {
        pass: isCorrect,
        message: () => {
            const toBe = Array.isArray(arg)
                ? arg.join(`' or '`)
                : arg;

            return `Expected '${received}' of '${receivedType}' type to be of '${toBe}' type(s)`;
        }
    };
}

});

You should check out my module (linked above). It does a bit more than that. But if that works for you: use it!

I think this is the way to go :)

describe('type check', () => {
    test('should be type string', () => {
        expect(typeof '').toBe('string')
    })

    test('should be type number', () => {
        expect(typeof 10).toBe('number')
    })

    test('should be type boolean', () => {
        expect(typeof true).toBe('boolean')
    })

    test('should be type undefined', () => {
        expect(typeof undefined).toBe('undefined')
    })

    test('should be type object', () => {
        expect(typeof { foo: 'bar' }).toBe('object')
    })

    test('should be type function', () => {
        expect(typeof function() {}).toBe('function')
    })

    test('should be type null', () => {
        expect(typeof null).toBe('object')
    })
})

It works like a charm as well as more readable and maintainable for the future.

I think this is the way to go :)

describe('type check', () => {
    test('should be type string', () => {
        expect(typeof '').toBe('string')
    })

    test('should be type number', () => {
        expect(typeof 10).toBe('number')
    })

    test('should be type boolean', () => {
        expect(typeof true).toBe('boolean')
    })

    test('should be type undefined', () => {
        expect(typeof undefined).toBe('undefined')
    })

    test('should be type object', () => {
        expect(typeof { foo: 'bar' }).toBe('object')
    })

    test('should be type function', () => {
        expect(typeof function() {}).toBe('function')
    })

    test('should be type null', () => {
        expect(typeof null).toBe('object')
    })
})
    test('should be type object', () => {
        expect(typeof { foo: 'bar' }).toBe('object')
        // passes
        expect(typeof ['foo', 'bar']).toBe('object')
        // passes
        expect(typeof null).toBe('object')
    })

😞

This is why I suggest my addon above: takes care of this.

InstanceOf is slightly better but prone to similar issues.

Link to it:

https://www.npmjs.com/package/jest-tobetype

thanks for solution @abritinthebay

Another solution:

expect('example').toEqual(expect.any(String));
expect(123).toEqual(expect.any(String));

The second one would fail with:

    Expected: Any<String>
    Received: 123
Was this page helpful?
0 / 5 - 0 ratings