V4 - Unit Testing

That is strange. I have been running this locally for a couple of moCould you specify:

  • Operating system
  • Strapi version

I see you are using Node16 and Yarn 1.22.

Yes, sure, thatā€™s macOS 13.0.1 (22A400), node v16.17.1, yarn 1.22.19, strapi 4.5.3

{
  "name": "my-project",
  "private": true,
  "version": "0.1.0",
  "description": "A Strapi application",
  "scripts": {
    "develop": "strapi develop",
    "start": "strapi start",
    "build": "strapi build",
    "strapi": "strapi",
    "test": "jest --forceExit --detectOpenHandles"
  },
  "devDependencies": {
    "jest": "^29.3.1",
    "sqlite3": "^5.1.2",
    "supertest": "^6.3.2"
  },
  "dependencies": {
    "@strapi/plugin-i18n": "4.5.3",
    "@strapi/plugin-users-permissions": "4.5.3",
    "@strapi/strapi": "4.5.3",
    "better-sqlite3": "7.4.6"
  },
  "strapi": {
    "uuid": "0a82effc-ea1b-4bfd-8508-9cb4d0c87a83"
  },
  "engines": {
    "node": ">=14.19.1 <=18.x.x",
    "npm": ">=6.0.0"
  },
  "license": "MIT"
}

When I remove the jest configuration from package.json and run jest without any configuration, the tests work as expected.

That is strange. I tested with Node16 this morning. You can see the package.json and other config details here: GitHub - StrapiShaun/unit-test-node16

Thanks, I tested it on my machine, still does not work, have no idea why. It works when I delete the jest config from the package.json.

To verify it from an independent machine, I forked your project and added GitHub actions. The tests succeed in the virtual environments, but fail on open db connections.

See my fork here: GitHub - n2o/unit-test-node16

And a sample failing GitHub Action: Copy sample env Ā· n2o/unit-test-node16@7de5467 Ā· GitHub

Iā€™ve got now working integration tests in my Strapi application. Additionally, I am using TypeScript (but did not use TypeScript in those testing projects).

To get Strapi v4 up and running with TypeScript and Integration Tests, one can look at this project: Strapi quickstart Typescript application with Jest - CodeSandbox

1 Like

hello guys

I have a project in Typscript, Iā€™ve been researching for two days, but so far I havenā€™t been able to run the Strapi Instance in my tests.

Can anyone who has had success point me to some material I can read?

Because the documentation I believe is out of date.

Thanks a lot for the help.

Hi @alcir-junior-caju you could check out this community userā€™s GitHub repo: GitHub - qunabu/strapi-unit-test-example: Strapi Unit test example

1 Like

Thank you very much. :smiley:

@alcir-junior-caju if it is working and you have a few extra minutes to write a high-level setup instructions for TypeScript I can add it to the documentation. It is on my list, but I havenā€™t had the time to work on it. There is an open PR ,which I need to refactor, here.

Coolā€¦ Iā€™m going to test this today and of course I write yes :slight_smile:

1 Like

Hello @Shaun_Brown Thanks to your help I got it :slight_smile:

Iā€™ll try to explain the additional steps, but some were my choice due to my organization of the tests ok.

But basically if you follow exactly what is done in that repository it will work.

As there is a compilation of the buildā€¦all tests will fail due to timing. To solve this just add this in each test:

...
beforeAll(async () => {
  await setupStrapi(); // singleton so it can be called many times
}, 1000000);

afterAll(async () => {
  await stopStrapi();
}, 1000000);
...

In my project I modified some settings because I prefer to run the integration and unit tests separately, but if you follow the repository settings, everything will work ok.

Iā€™ll send a gist with the settings I usedā€¦

Otherwise everything is exactly as in the repository.

I tested false positives and apparently all tests are working, but please check to see if everything is really ok.

Iā€™m going to start the tests here, understand what I can test, correctly separate integration and unit tests, so I may have made a mistake in some step using Strapi.

So it would be good for more people to test and see if this flow of tests is really ok.

1 Like

Updating my findings.

In my project I have several permissions.

Just with the Factory from the repository was not enough as it only returns the user Authenticated.

So I had to move the factory from User to reuse elsewhere, and I made minor modifications.

First I had to populate the Permissions so I created a Factory for that:

Then create a Factory for Role:

And finally I made small adjustments to the Factory of the original User:

Now to test my Endpoint with the user and its correct permissions, I simply in beforeAll create the permissions to role the user and get the jwt:

It worked great, I was able to raise the strapi instance, register permissions, roles, user to be able to test my account.

Again I donā€™t know this is the best way to use the tests, but it worked fine.

1 Like

There is also an option to test private points using an API key with full or partial access. Maybe it will be useful for someone. The key can be created in the global scope or in the test scope

const getRandomID = () => {
  return Math.round(Math.random() * 10000).toString();
};

const getFullAPIToken = async () => {
  const tokenService = strapi.service("admin::api-token");

  const attributes = {
    name: `token${getRandomID()}`,
    description: "",
    type: "full-access",
    lifespan: null,
  };

  const apiToken = await tokenService.create(attributes);
  return apiToken.accessKey;
};

// Test
let token;

beforeAll(async () => {
    token = await getFullAPIToken();
});

it("should get categories", async () => {
    await request(strapi.server.httpServer)
      .get("/api/category-list")
      .set("accept", "application/json")
      .set("Authorization", `Bearer ${token}`)
      .expect("Content-Type", /json/)
      .expect(200)
});
2 Likes

Thanks for the code snippet for the getFullAPIToken(), itā€™s really helpful for API token related tests.

Curious on how I can add specific permissions to my API token, letā€™s say only add api::restaurant.restaurant.find if type of my API token is custom (if thereā€™s such value).

1 Like

Configuring attributes like this worked for me:

  const attributes = {
    name: `token${getRandomID()}`,
    description: "",
    type: "custom",
    lifespan: null,
    permissions: [
      "api::restaurant.restaurant.find",
    ]
  };