Stubbing

We use sinonjs to stub test methods.

If you use sinon, don't forget to call sinon.restore so that stubs don't leak into subsequent tests. This is done automatically when you use setupBeforeAfter helpfer function

When stubbing tests in the plugin, we following the following pattern

suite("GIVEN some test", function () {
  // before can also go inside `describeMultiWS`, or some other describe block.
  // The block you put it in will be the block where it is applied.
  before(async () => {
    // async is optional, you can use a regular function too
    const stubbedProperty = sinon.stub(Thing, "property");
    // If you are stubbing a function, you can pick what it returns
    stubbedProperty.returns(42);
    stubbedProperty.resolves("will be returned as a promise");
  });

  describeMultiWS("WHEN using something", {}, () => {
    test("THEN thing happens", async () => {
      // Test the thing here
    });
  });
  // describeMultiWS and describeSingleWS automatically restore all stubs after all the tests are done.
});

If you need something to be re-stubbed for each test (rather than the whole block), you can use the beforeEach hook to do so. You will need to restore the stub yourself in that case.

beforeEach(() => {
  sinon.stub(Thing, "property");
});

afterEach(() => {
  // Required, `describe*WS` doesn't restore things until all tests are over so `beforeEach` hook will be missed
  sinon.restore();
});

Stubbing Mutating Disk Changes

Dendron has certain features that writes to locations that are global to all Dendron installations (eg. ~/.dendron). Because we run integration tests on these features, we also don't want these tests to overwrite our actual dendron setup. The tests for these sort of features should be stubbed.

For example, below is an example of stubbing the home directory for tests:

static mockHomeDir(dir?: string): SinonStub {
  if (_.isUndefined(dir)) dir = tmpDir().name;
  return sinon.stub(os, "homedir").returns(dir);
}

Stubbing dendron.yml in a test

Stubbing dendron.yml in a test

test("backlink to home page", async () => {
  await runEngineTestV5(
    async ({ engine, vaults }) => {
      ...
    },
    {
      expect,
      preSetupHook: async (opts) => {
        ...
        TestConfigUtils.withConfig(
          (config) => {
            config.site = {
              siteHierarchies: ["alpha"],
              siteNotesDir: "docs",
              siteUrl: "https://foo.com",
              siteRootDir,
            };
            return config;
          },
          {
            wsRoot,
          }
        );
      },
    }
  );
});

Creating a mock note

Use NoteTestUtils

Stubbing setTimeout

Stubbing the global setTimeout (with sinon.fakeTimer or else) seems to break VSCode, causing it to just hang. If you are having trouble with that, instead use Wrap.setTimeout from @dendronhq/common-all in the code you want to test. Then you can stub the wrapped function and simulate the timer going off with stub.callArg(0). For example:

const stubTimeout = sinon.stub(Wrap, "setTimeout");
const editor = await WSUtils.openNote(note);
WorkspaceWatcher.moveCursorPastFrontmatter(editor);
stubTimeout.callArg(0);

Stubbing global functions

Follow the same procedure as Stubbing, but create your own wrapper if one doesn't exist. You can reuse the Wrap class in common-all. Try to wrap the function exactly with the same signature.

const cmd = new DoctorCommand(extension);
const reloadSpy = sinon.stub(cmd, "reload" as keyof DoctorCommand);

Backlinks