I am trying out the SDF unit testing. I am having ...
# suitescript
m
I am trying out the SDF unit testing. I am having trouble with search results. I have mocked the following.
Copy code
import search from "N/search";
import Search from "N/search/instance";
import ResultSet from "N/search/resultSet";

jest.mock("N/search");
jest.mock("N/search/instance");
jest.mock("N/search/resultSet");
describe("Search test", () => {
    it("will results set be looped through?", () => {
        search.create.mockReturnValue(Search);
        Search.run.mockReturnValue(ResultSet);
        ResultSet.each.mockReturnValue([{id: 5}]);
    });
});
with the following in the script
Copy code
search.create({
                    type: "record_type"
                    , filters: [
                        {name: "status", operator: search.Operator.NONEOF, values: ["3", "4"]}
                    ]
                }).run().each(function(results) {
                    console.log({results});
                    complete = false;
                });
complete
never gets swapped to false and the
console.log
isn't logged. Anybody mocked this before?
👀 1
n
No, I haven't but maybe take a look at this: https://github.com/KyleJonesWinsted/suitecloud-unit-testing-stubs
👀 1
p
also interested in this subject, but haven't toyed with it yet that much. any luck in the #C42JX79UZ channel? It is closer to the subject i guess
e
I typically do my best to separate the business logic from the NetSuite API calls for this reason. A unit test full of mocks isn't generally a useful test. If you isolate the search creation and execution into its own function, then you can just mock that function in your unit test rather than having to mock the search API. If you isolate the iterator logic into its own function, then you can likely unit test that in isolation as well.
🙌 2
b
you are mocking the return value of ResultSet.each which is uninteresting, it doesnt return anything
you are interested in its side effect, which is that it calls the callback function once for each search result (usually, there is some control over when to stop)
if you are going through the trouble of mocking the search code, then you need to use a mock implementation
you arent usually interested in writing tests on the functionality of N/search
💯 1
its not your code, you are only really interested in passing a working function as the first parameter of ResultSet.each
so you should really be testing your callback function
m
So I came to the same realization that
.each
doesn't return. But I couldn't figure out how to mock implementation so I changed to using
N/query
in this instance since
.asMappedResults
returns a value making it easier to mock. I am very new to Jest and testing so I appreciate all the feedback.
e
My (very) generic design approach is:
Copy code
netsuiteStuff = getStuffFromNetsuite(); // Use NS APIs to retrieve data; don't unit test this
processedStuff = doBusinessyThings(netsuiteStuff); // avoid NS APIs; unit test this
storeStuffBackInNetsuite(processedStuff); // Use NS APIs to store data; don't unit test this
m
Curious what syntax you use in the testing to mock return values from those functions? I tried creating a mock for the function and mocking a return value, but it didn't seem to work for me. For example how would you mock return values from
getStuffFromNetsuite()
within your script? I tried something similar to the following.
Copy code
const getStuffFromNetsuite = jest.fn();

getStuffFromNetsuite.mockReturnValue(true);
and
Copy code
const getStuffFromNetsuite = jest.fn().mockImplementation(() => true);
e
I wouldn't; I'd only be unit testing
doBusinessyThings
, so I don't need to care about
getStuffFromNetsuite
or
storeStuffBackInNetsuite
in my tests at all.
netsuiteStuff
would be a plain JavaScript Object (or other vanilla JS data structure), so my test would be something like:
As often as feasible, I normalize data coming from NetSuite or any other system into a vanilla JS structure first before I do any logic or processing on it. This way, I'm always working with known and vanilla structures that I do not need to mock
m
So it sounds like you don't use the stubs from
@oracle/suitecloud-unit-testing
at all? My first attempt of Jest was pretty much what you did...just test certain functions. I wanted to mock a more top to bottom test though. Maybe I am overdoing it though.
e
I do use stubs when I absolutely have to, but I do not use `@oracle/suitecloud-unit-testing`; I replace that with the aforementioned
suitecloud-unit-testing-stubs
We do try to use Jest for more end-to-end tests of entry points sometimes, but it always feels very brittle to me because of all the mocking we have to do
m
Thanks I appreciate walking me through this.
e
With
suitecloud-unit-testing-stubs
, mocks might look like these highly contrived tests:
m
This is an example of an M/R getInputData test suite that I recently created with
"N/query"
.
Copy code
/* eslint-disable max-len */
// import script
import script from "../src/FileCabinet/SuiteScripts/cafb-scripts/map-reduce/pdd-create-update-transactions.js";
import testing_data from "../__test_data__/testing_data.js";

// import Netsuite stubs that will be used
import query from "N/query";
import Query from "N/query/instance";
import ResultSet from "N/query/resultSet";

// create mocks from the stubs
jest.mock("N/query");
jest.mock("N/query/instance");
jest.mock("N/query/resultSet");

const inputContext = {
    isRestarted: false
};

beforeEach(() => {
    jest.clearAllMocks();
});

describe("Testing getInputData entry point", () => {
    it.each(testing_data)("$name", ({name, input}) => {
        // given
        query.create.mockReturnValue(Query);
        Query.run.mockReturnValue(ResultSet);
        ResultSet.asMappedResults.mockReturnValue(input.query_results);

        // when
        const input_result = script.getInputData(inputContext);

        // then
        expect(input_result).toStrictEqual(input.query_results);
    });
});
Now that I review it it's kind of a useless test since I removed a lot of logic from that step in this instance.
e
To be clear, I am also not a unit testing expert. I'm sure many a TDD developer would be appalled at my tests.
but I do think the approach of separating out NetSuite APIs from business logic leads to more readable code and tests. I do not think Jest is the right tool for end-to-end testing of entry points, but I do not have a better one at the moment.
👍 1
b
in general you do not want to test any of the entry points, that means you have to mock everything
break up your code into smaller function s to test
e
I'd agree except I'd add the word unit in front of test - you don't want to unit test the entry points. You do probably want to test them, either manually or automatically, before they get to production
That's me being pedantic though
We did write a M/R script that creates a bunch of records and passes them through some logic and then compares the changes in the records, effectively building our own native test harness. This works better for testing entry points, and is similar to what the Xcelero SuiteApp does, but it is a lot of work
m
@Philip Denys if your interested I was able to mock a search by changing from
run()
to
runPaged()
because the fetch will return. Here is the search syntax.
Copy code
const paged_data = search.create({
    type: "recordtype"
    , columns: [column_field]
    , filters: [
        {name: filter_field, operator: search.Operator.ANYOF, values: [parseInt(source_class)]}
    ]
}).runPaged();

paged_data.pageRanges.forEach((range) => {
    const page = paged_data.fetch({index: range.index});
    page.data.forEach((result) => {
        mapped_class = result.getValue(column_field);
});
Here are sections of the mock.
Copy code
import search from "N/search";
import Search from "N/search/instance";
import SearchPagedData from "N/search/pagedData";
jest.mock("N/search");
jest.mock("N/search/instance");
jest.mock("N/search/pagedData");

SearchPagedData.fetch = jest.fn();

describe("test", () => {
    it("test case", () => {
        search.create.mockReturnValue(Search);
        Search.runPaged.mockReturnValue(SearchPagedData);
        jest.replaceProperty(SearchPagedData, "pageRanges", [{index: 0}, {index: 1}]); // test looping through multiple pages
        SearchPagedData.fetch.mockImplementation((options) => {
            if (options.index === 0) {
                return {
                    data: [
                        {getValue: jest.fn(() => 2)}
                    ]
                };
            } else if (options.index === 1) {
                return {
                    data: [
                        {getValue: jest.fn(() => 3)}
                    ]
                };
            }
        });
    });
});
👀 1
p
i'll be testing this out, thanks for sharing 😉