Juliën
Juliën

Reputation: 9552

Generic test in TypeScript that asserts each property on a class has a value assigned

I have a TypeScript +2.4 project where I'm using Jest for my unit tests. The project has a lot of poco models, without a default value. For example:

export class Foo {
    public id: number
    public name: string;
    public when: Date;
}

Each of these models is mapped from raw json to this class. It is a requirement for my tests that all properties are assigned, e.g. have values. This leads to the following test that has to be written for all models:

test('Foo() should have its properties assigned', () => {
    const target: Foo = {
        id: 1001, name: 'whatever', when: new Date()
    };

    // manually assert each propertie here
    expect(target.id).toBeDefined();
    expect(target.name).toBeDefined();
    expect(target.when).toBeDefined();
}

To me, that's not so DRY to do for each test. Not to mention error prone and cumbersome. What I would like to do is create a helper that iterates through each property and asserts that it has a value assigned.

Example 1 - Object.keys
This example is incorrect because Object.keys only iterates through the already assigned properties, ignoring the non-set properties (and thus always is positive):

public static AssertAllPropertiesAreAssigned(target: object): void {
    Object.keys(target).forEach((key, index) => {
        expect(target[key]).toBeDefined();
});

Example 2 - Object.getOwnPropertyNames()
The same as example 1:

public static AssertAllPropertiesAreAssigned(target: object): void {
    Object.getOwnPropertyNames(target).forEach((name, index) => {
        expect(target[name]).toBeDefined();
});

Example 3 - Set default values
By assigning a default value to each poco, like null, I can make the earlier samples work. But I'd sure like to avoid that at all cost:

export class Foo {
    public id: number = null;
    public name: string = null;
    public when: Date = null;
}

The question: is there a way to create a helper that asserts that each property of my TypeScript poco object is actually assigned a value, in my test? Or, as an alternative, does Jest have some util for this?

There are similar questions on SO, but they are not related to asserting the values in a test. This makes this question, as far as I've looked around, differ from the others:

Also, I'm aware that the Javascript compiled output of my poco will probably leads to that the unset properties are simply not available:

var Foo = (function() {
    // nothing here...
}());

But with TypeScript's strong typing power and recent changes and/or Jest helpers, there might be some additional options to get this done?

Upvotes: 0

Views: 1866

Answers (1)

jcalz
jcalz

Reputation: 329168

Most of your options aren't any better than the answers to those other questions: initialize the properties (good idea); use property decorators (tedious).

Personally, I think it should be an error to declare a class property as a can't-be-undefined type like string and then not define it in the constructor, but that feature isn't part of TypeScript yet, even if you turn on strictNullChecks (which you should). I don't know why you don't want to initialize the variables, but this would work:

export class Foo {
    public id: number | undefined = void 0;
    public name: string | undefined = void 0;
    public when: Date | undefined = void 0;
}

Now an instance of Foo will have the relevant keys if you do Object.keys() even though the values will still be undefined.


Wait a minute, you're not even using the class at runtime:

const target: Foo = { 
    id: 1001, name: 'whatever', when: new Date()
}; // object literal, not constructed class instance
console.log(target instanceof Foo) // false

Then I suggest you use an interface instead of a class, and just turn on strictNullChecks:

export interface Foo {
    id: number;
    name: string;
    when: Date;
}

const target: Foo = {
    id: 1001, name: 'whatever', when: new Date()
};
const badTarget: Foo = {
    id: 1002; 
}; // error, Property 'name' is missing

Now TypeScript will not let you assign a possibly-undefined value to those properties and you don't have to bother looping over anything at runtime.

Hope that helps!

Upvotes: 1

Related Questions