Difficulties with Jasmine tests and TestApi

#1

Hello,

I’m having trouble writing Jasmine tests with intricate data on 7.6.

  • I’m exclusively creating objects with TestApi.createEntity. I was hoping they would get deleted with:
afterAll(function () {
     TestApi.teardown(ctx);
     expect(ctx.numObjects()).toEqual(0); // this test passes: tracked objects are deleted, but some remain
});

But when I run my test, while they are all created (150+ objects), some objects don’t get deleted. Objects that don’t get deleted look like they are not being tracked despite the use of TestApi.createEntity:

TestApi.createEntity(ctx, tn, obj);
console.log(ctx.findObjectById(obj.id)); // prints the object in most cases, but sometimes null; in that case object is not deleted during teardown

Subsequent runs of the test fail because the entities already exist (therefore can’t be created).

  • Foreign relations are not populated: for instance I have a field ratePlanHistory: [ServiceToRatePlanRelation](from) that remains empty after a fetch, even though the proper ServiceToRatePlanRelation objects have been created. I don’t know how to force C3 to update these relations.

These issues prevent us from building complex test cases. How do you work around them?

Thanks for any advice!

0 Likes

#2

It seems that the root of the issue is: “sometimes, when I create an object using TestApi.createEntity, that object is not removed by a subsequent TestApi.teardown()”.

Is that accurate? Can you provide a repro for me? (e.g. a particular call to createEntity followed by a teardown that leaves the object around?)

0 Likes

#3

Foreign key fields like this are evaluated on the fly, so there is no need nor any way to “force” population. However, for performance reasons, they are returned as empty on fetches without explicit FetchSpec.include. So just make sure you are asking for that foreign key field to be included with the fetch result (e.g. MyType.fetch({include: "ratePlanHistory"}))

0 Likes

#4

Thanks for your answer Riley & Matt.

Problem seems to come from nested types. When an entity with a foreign relationship is created, the foreign object is created but is not attached to the TestApi context. Therefore, everything breaks later on as those foreign objects are not deleted by the teardown.
Since I’m creating objects through canonicals with complex relationships, I cannot ensure the objects are always created with .createEntity before they are referenced by another object.
In any case, it seems like a major bug to me that TestApi.createEntity would only keep track of the provided object and not the nested objects that are also created.

Following test demonstrates the issue:

/*
 * Test of the test api
 */
var filename = 'testApiIssue';

describe(filename, function () {

    var ctx;

    beforeAll(function () {
        ctx = TestApi.createContext(filename);

        // Ensure the object Service does not exist
        console.log("Before: " + Service.fetchCount({
            filter: Filter.eq('id', 'SVC.TEST001.XAD')
        }));
    });

    it("properly creates ServicePoint", function() {
        // This will create ServicePoint "SP.TEST001.XAD" and attach it
        // to TestApi, but this will also create Service 'SVC.TEST001.XAD'
        // and it will not attach it to TestApi
        TestApi.createEntity(ctx, 'ServicePoint', {
            id: 'SP.TEST001.XAD',
            services: [{id: 'SVC.TEST001.XAD'}]
        });

        // Prints 1 instead of 2
        console.log("Tracked objects: " + ctx.numObjects());

    });

    it("properly creates Service", function() {
        // This fails because the Service has been created by the
        // ServicePoint creation, but the Service object is not
        // attached to `ctx`!
        TestApi.createEntity(ctx, 'Service', {
            id: 'SVC.TEST001.XAD'
        });
    });

    afterAll(function () {
        TestApi.teardown(ctx);

        // This works
        expect(ctx.numObjects()).toEqual(0);

        // But the service is still around
        console.log("After: " + Service.fetchCount({
            filter: Filter.eq('id', 'SVC.TEST001.XAD')
        }));
    });
});

This yields following output (simplified the trace):

tester: executing test_api.js (1525b)
Before: 0
Tracked objects: 1
all.js?env=browser&compat:19712 POST <...>/Service?action=create 500 (Server Error)
main.js:1696 tester: "testApiIssue properly creates Service" failed:
main.js:1698   1: ActionError: Write failed: The specified ID 'SVC.TEST001.XAD' already exists for type Service. Please change to a unique ID.
After: 1
main.js:1722 tester(test_api.js): 1 passed, 1 failed
0 Likes

#5

Building on this discovery, it seems that creating objects with:

TestApi.createEntity(ctx, tn, obj, {ignoreFkeyFields: true});

is a reasonable workaround − as long as all foreign objects are created manually afterwards.

1 Like

#6

This is a great example of why Metadata/Configuration is better than code.

Since the logic that creates a Service when a ServicePoint is created is encoded in javascript, there’s no way for TestApi to know that creating a ServicePoint ALSO creates a Service.

If it were instead indicated in the type system itself that creating a ServicePoint also creates a Service, TestApi could react to that knowledge.

Anyway, what you have discovered is not a bug in TestApi. One of the following is the case:

A) a bug in ServicePoint: ServicePoint should declare that a Service is also created through the typesytem
B) a bug in ServicePoint: ServicePoint should not also create a Service automatically
C) a bug in the type system -> (A) above might not be possible
D) a bug in ServicePoint -> Just as ServicePoint automatically creates a Service, it shoudl automatically remove the service.

Probably A, B, and C are the true bugs and D is a mistake (going deeper into the mistake of declaring relationships in code, not in metadata)

0 Likes