Traits

Traits are collections of attributes and transients that can be applied to a given fixture, during definition or at creation time. They can be defined globally or in a given fixture. If defined globally, any fixture may reference it, while if defined in a fixture, only that fixture and all child fixtures may reference it. (In other words, traits are lexically scoped.)

Defining traits

Traits are defined just like fixtures, and can be referenced when creating fixtures or child fixtures with the {traits: []} option.

// global trait
fr.trait("old", (t) => {
    t.attr("age", () => 100);
});

fr.fixture("user", User, (f) => {
    f.attr("name", () => "Noah");
    f.attr("age", () => 32);
    f.attr("isAdmin", () => false);

    // fixture trait
    f.trait("admin", (t) => {
        t.attr("isAdmin", () => true);
    });

    f.fixture("adminUser", User, {traits: ["admin"]});
    f.fixture("oldUser", User, {traits: ["old"]});
    f.fixture("oldAdmin", User, {traits: ["admin", "old"]});
});

const user = await fr.build("oldAdmin");
user.isAdmin
// true
user.age
// 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

In fixture and trait definitions

Traits can be applied to a fixture inside of the definition, by referencing them like attributes or relations:

fr.trait("old", (t) => {
    t.attr("age", () => 100);
});

fr.fixture("user", User, (f) => {
    f.attr("name", () => "Noah");

    // references the global trait
    f.attr("old");
});

const user = await fr.build("user");
user.age
// 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14

NOTE

Fixtures and sequences are checked before traits are, so if you try to reference a trait that shares a name with a fixture or sequence, it will reference those instead.

Precedence

Traits follow the [scoping] rules, so if multiple traits would affect the same attribute, the last trait is used:

fr.fixture("user", User, (f) => {
    f.trait("old", (t) => {
        t.attr("age", () => 100);
        f.attr("favoriteColor", () => "black");
    });

    f.trait("young", (t) => {
        t.attr("age", () => 5);
        f.attr("favoriteColor", () => "red");
    });

    f.trait("faveBlue", (t) => {
        f.attr("favoriteColor", () => "blue");
    });

    f.fixture("youngUser", {traits: ["young", "faveBlue"]});
    f.fixture("oldUser", {traits: ["faveBlue", "old"]});
});

const youngUser = await fr.build("youngUser");
youngUser.age
// 5
youngUser.favoriteColor
// blue

const oldUser = await fr.build("oldUser");
oldUser.age
// 100
oldUser.favoriteColor
// black
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

When building an instance

As shown above, traits can be passed into factories and child factories using the {traits: []} options, and they can be referenced in fixture and trait definitions.

They can also be used when building an instance of a fixture by passing in an array of trait names:

fr.fixture("user", User, (f) => {
    f.attr("name", () => "Noah");
    f.attr("age", () => 32);

    f.trait("old", (t) => {
        t.attr("age", () => 100);
    });

    f.trait("clown", (t) => {
        t.attr("name", () => "Pagliacci");
    });
});

const sillyNoah = await fr.build("user", ["old", "clown"]);
sillyNoah.name;
// Pagliacci
sillyNoah.age;
// 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

This works with all strategies, including all Pair and List variations:

const savedClowns = await fr.createList("user", 3, ["clown"]);
savedClowns.length;
// 3
savedClowns[0].name;
// Pagliacci
1
2
3
4
5

With relations

Traits can be passed to relations, just like when building instances:

fr.fixture("user", User, (f) => {
    f.attr("name", () => "Noah");

    f.trait("clown", (t) => {
        t.attr("name", () => "Pagliacci");
    });
});

fr.fixture("post", Post, (f) => {
    f.relation("user", ["clown"]);
    // or
    f.relation("author", ["clown"], {fixture: "user"});
});

const post = await fr.create("post");
post.user.name
// Pagliacci
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17