Debajit Majumder
Debajit Majumder

Reputation: 924

JEST: Is there a solution to run tests only for changed/impacted files?

There are few solutions given by Jest in that direction. I have tried out most of the solutions like --findRelatedTests, --onlyChanged, --changedSince. But there are few shortcomings in every solutions. I thought --changedSince is the best match for me.

jest --changedSince=origin/master --coverage

It mostly covers the basic scenarios like running test files corresponding to the changed source files. But it does not handle few scenarios like if a source-file(say a.js) is deleted and same(a.js) is being used(imported) in another file(say b.js), it does not run tests for any of the files(a.js or b.js). It does not seem to run tests for parent files where it was imported.

Is there a clean solution which can handle all the scenarios like file rename/deletion, dynamic imports, running tests for the parent modules where it was imported or any other impact that may happen when you change a source file?

Upvotes: 14

Views: 16241

Answers (1)

Pouya Ataei
Pouya Ataei

Reputation: 2169

Quick answer: No.

Long answer: Yes, but it's not that clean or straight forward.

I have achieved this through three steps.

STEP 1:

You can achieve this by a bit of scripting. First you'll want to get a list of all the changed files through Git.

This can be achieved through a function like the below:

const util = require("util")
const exec = util.promisify(require("child_process").exec)

const detectChangedFiles = async () => {
    try {
        const { stdout, stderr } = await exec("git diff origin/master --name-only")

        if (stderr) {
            throw new Error(stderr)
        }

        return stdout.replace(/\n/g, " ").replace(/client\//g, " ")

    } catch (error) {
        console.log(error)
    }
}

STEP 2:

Secondly, you'd wanna get a list of related tests for those files like the below:

 const findRelatedTests = async () => {
    const changedFiles = await detectChangedFiles()

    try {
        const { stdout, stderr } = await exec(`jest --listTests --findRelatedTests ${changedFiles}`)

        if (stderr) {
            throw new Error(stderr)
        }

        if (!stdout) {
            console.log('No tests found for the changed files :)')
        } else {
            return stdout.replace(/\n/g, " ")
        }

    } catch (error) {
        console.log(error)
    }
}

STEP 3:

And finally you'd wanna feed all of those tests to jest to run;

const runRelatedTests = async () => {
    const relatedTests = await findRelatedTests()

    if (relatedTests) {
        try {
            const { stdout, stderr } = await exec(`jest --ci --coverage ${relatedTests}`)

            if (stderr) {
                throw new Error(stderr)
            }
        } catch (error) {
            console.log(error)
        }
    }
}

One of the limitations of this implementation is that I"m always diffing against the master and that's not a good assumption. In special cases, one may chose to merge against another branch.

This can be handled in a few ways;

  1. If you're running a cli, pass the arguments to the cli and consume it in your script
  2. If you're running in pipeline like Gitlab and assuming that this is a MR/PR, consider using some available environment varibale ( in this case CI_MERGE_REQUEST_TARGET_BRANCH_NAME )

Upvotes: 14

Related Questions