Writing and testing Script Includes using Fluent

kenf
Tera Contributor

@patrick_wilson 

 

I'm attempting to use Fluent with the ServiceNow SDK. I'm trying to understand if this can improve the workflow I already have. I think it would be most useful to me when I'm writing scripts, but I'm struggling to understand how I can execute and test those scripts in VS Code.

 

It seems like scripts in Fluent are basically strings, but I don't want to work with strings in VS Code. If I am developing a Script Include in VS Code I want to work with the real types. Maybe I'm missing something.

 

When I write JavaScript for ServiceNow I try to keep most of the JavaScript out of specific Business Rules, UI Actions, Script Actions, Scripted REST APIs, etc. I try to keep JavaScript in Script Includes. I create objects in Script Includes and call those objects from the various ServiceNow artifacts.

 

I find using Script Includes allows for easier code reuse and testing. From the testing perspective both manual and automated testing.

 

I develop using ServiceNow Extension for VS Code. I can write my Script Include in VS Code and manually execute background scripts while developing the Script include. That gives me all the type safety in VS Code and the ability to execute the code. A lot of times I have Script Includes that reference things in other Script Includes and VS Code can properly resolve all the connections.

 

Then I can also write my automated test for the Script Include in VS Code. I do have to execute the test through ServiceNow, but the test itself is written in VS Code. These are ATF tests that I write. I'll provide an example below.

 

For a simple example. This is a code snippet from one of my Script Includes. To level set, in this code there is a naming convention. Functions prefixed with on are called by Business Rules, functions prefixed with show are UI Action conditions, and functions that follow a show function are the script for the UI Action. It doesn't really matter. This is Script Include code that I'm able to manually test through a background script or automated test through ATF.

/**
 * Values for x_912467_klf_todo_task.status field
 * @type {{DRAFT:string, SUBMITTED:string, IN_PROGRESS:string, COMPLETED:string, REJECTED:string, CANCELED:string}}
 */
const STATUS = {
    DRAFT: 'draft',
    SUBMITTED: 'submitted',
    IN_PROGRESS: 'in_progress',
    COMPLETED: 'completed',
    REJECTED: 'rejected',
    CANCELED: 'canceled',
};

const groupUtils = GroupUtils;

class TaskManager {
    constructor() {
        this.STATUS = STATUS;
    }

    /**
     * Responsible for filtering the tasks that the user can see
     * Admin can see everything
     * User can see tasks they've submitted
     * User can see tasks that are assigned to them or their group
     * @param {GlideRecord} task
     */
    onQuery(task) {
        if (Authenticated.isAdmin()) return;

        task.addQuery('opened_by', Authenticated.getUserSysId())
            .addOrCondition('assigned_to', Authenticated.getUserSysId())
            .addOrCondition('assigned_group', 'IN', Authenticated.getGroups());
    }

    /**
     * Called by before business rule when task is completed
     * @param {GlideRecord} task
     */
    onComplete(task) {
        task.closed_on = new GlideDateTime();
        task.closed_by = Authenticated.getUserSysId();
    }

    /**
     * Show Reject UI Action whenever we show Approve UI Action
     * @param {GlideRecord} task x_912467_klf_todo_task to check
     * @returns {boolean}
     */
    showReject(task) {
        return this.showApprove(task);
    }

    /**
     * UI Action to approve a task. This approves the task
     * @param {GlideRecord} task x_912467_klf_todo_task to approve
     */
    reject(task) {
        task.status = STATUS.REJECTED;
        task.update();
    }

    /**
     * Returns true if user has access to "Approve" UI Action
     * Show approve when task is in progress or submitted status
     * @param {GlideRecord} task x_912467_klf_todo_task to check
     * @returns {boolean}
     */
    showApprove(task) {
        return this.canWrite(task) && this.isInAssignedGroup(task) && task.status == STATUS.SUBMITTED;
    }

    /**
     * UI Action to approve a task. This approves the task
     * @param {GlideRecord} task x_912467_klf_todo_task to approve
     */
    approve(task) {
        task.status = STATUS.IN_PROGRESS;
        task.update();
    }
}

 

If I wanted to manually test through a background script ServiceNow Extension for VS Code provides a background scripts folder. I can write a script that exercises the TaskManager. If I was working on the TaskManager.approve function I could write something like the following:

const task = new GlideRecord('x_912467_klf_todo_task');
task.addQuery('active', true);
task.setLimit(1);
task.query();

TaskManager.approve(task);
gs.info('Is task in progress? ' + task.getValue('status'));

This allows me to stay in VS Code and iterate on this function until I get it right.

 

For automated testing I use the Run Server Side Script test step to execute a Jasmine test. The code in the test step looks like the following:

TaskManagerTest(outputs, steps, params, stepResult, assertEqual);
jasmine.getEnv().execute();

All it's doing is executing a Script Include that has the actual test. Then inside of that Script Include looks like this:

 

function TaskManagerTest(outputs, steps, params, stepResult, assertEqual) {
    describe('TaskManagerTest', function () {
        describe('showApprove()', function () {
            it('should return false when task is in Draft', function () {
                const task = createDraftTask(testUtils).task;
                expect(TaskManager.showApprove(task)).toBe(false);
            });

            it('should show approve when task is in Submtted and user is approver', function () {
                const { approverUser } = createApprover(testUtils);
                const task = createSubmittedTask(testUtils).task;

                testUtils.impersonateUser(approverUser.getUniqueValue());
                expect(TaskManager.showApprove(task)).toBe(true);
            });

            it('should not show approve when task is in Submtted and user is not approver', function () {
                const { task, submitter } = createSubmittedTask(testUtils);

                testUtils.impersonateUser(submitter.getUniqueValue());

                expect(TaskManager.showApprove(task)).toBe(false);
            });

            it('should return false when task is in Completed', function () {
                const task = createCompletedTask(testUtils).task;
                expect(TaskManager.showApprove(task)).toBe(false);
            });
        });

        describe('approve()', function () {
            it('should set state to In Progress', function () {
                const { approverUser } = createApprover(testUtils);
                const { task } = createSubmittedTask(testUtils);

                testUtils.impersonateUser(approverUser.getUniqueValue());

                TaskManager.approve(task);

                expect(task.getValue('status')).toBe(TaskManager.STATUS.IN_PROGRESS);
            });
        });
    });
}

 This allows me to keep most of my script development in VS Code and all the tooling support VS Code has. With Fluent if I'm forced to define scripts as template strings it seems like I'm losing a lot of the value of developing in VS Code.

 

Can you guys give more of an example of how you would develop and test a script in VS Code using Fluent. I've watched a couple demos, but still seem to be missing something.

1 ACCEPTED SOLUTION

sadif_raja
Tera Guru

 

Hey @kenf 

It sounds like you're trying to integrate Fluent with your current Script Include workflow in VS Code, but running into issues with type safety and script execution. Fluent tends to treat scripts as strings, which can be limiting.

**Solution:**

1. **Stick to Script Includes**: Continue writing your complex logic in Script Includes and use Fluent selectively, mainly for operations like GlideRecord queries or simple data manipulations.

2. **Fluent Integration**: You can embed Fluent for record handling within Script Includes to avoid working with strings. For example, use Fluent for basic CRUD operations inside your existing Script Includes.

```javascript
approve: function(task) {
var fluentTask = Fluent.record('x_912467_klf_todo_task').get(task.getUniqueValue());
fluentTask.setValue('status', this.STATUS.IN_PROGRESS).update();
}
```

3. **Testing**: Keep testing Script Includes manually with background scripts or through ATF as you currently do in VS Code. Fluent doesn’t interfere with ATF tests, and you can still use Jasmine for automated tests.

This way, you can maintain structured code in VS Code and leverage Fluent when needed. Let me know if you need more detailed examples!

Hope this helps!

 

View solution in original post

2 REPLIES 2

sadif_raja
Tera Guru

 

Hey @kenf 

It sounds like you're trying to integrate Fluent with your current Script Include workflow in VS Code, but running into issues with type safety and script execution. Fluent tends to treat scripts as strings, which can be limiting.

**Solution:**

1. **Stick to Script Includes**: Continue writing your complex logic in Script Includes and use Fluent selectively, mainly for operations like GlideRecord queries or simple data manipulations.

2. **Fluent Integration**: You can embed Fluent for record handling within Script Includes to avoid working with strings. For example, use Fluent for basic CRUD operations inside your existing Script Includes.

```javascript
approve: function(task) {
var fluentTask = Fluent.record('x_912467_klf_todo_task').get(task.getUniqueValue());
fluentTask.setValue('status', this.STATUS.IN_PROGRESS).update();
}
```

3. **Testing**: Keep testing Script Includes manually with background scripts or through ATF as you currently do in VS Code. Fluent doesn’t interfere with ATF tests, and you can still use Jasmine for automated tests.

This way, you can maintain structured code in VS Code and leverage Fluent when needed. Let me know if you need more detailed examples!

Hope this helps!

 

kenf
Tera Contributor

@sadif_raja,

 

Thanks for the response. That makes sense. I'm still a bit confused of the purpose of Fluent. Is it more for ServiceNow tooling, or is it something that is useful to build custom applications?

 

Right now, I don't really see the point in using it for building custom applications. I've got a better understanding of how to use Fluent and JavaScript modules now and it doesn't seem like this makes development any easier.

 

I'm trying to understand why I would use a JavaScript module as a way to share code as opposed to a Script Include? Why would I create ServiceNow artifacts using Fluent as opposed to the ServiceNow UI?

 

I like the idea of using TypeScript, but ServiceNow already seems to have some kind of transpiler. If they wanted to add TypeScript as an option I'd think they could add that as one of the JavaScript modes of a scoped application.

 

It's nice to have options, but it would be nice to know what is enabled by developing with Fluent and JavaScript modules versus the traditional way we develop now.