Skip to content

refactor(test): replace findOne with findOneOrFail to eliminate non-null assertions - #12325

Merged
pkuczynski merged 10 commits into
masterfrom
refactor/tests-use-find-or-fail
Apr 8, 2026
Merged

pkuczynski merged 10 commits into
masterfrom
refactor/tests-use-find-or-fail

Conversation

@pkuczynski

@pkuczynski pkuczynski commented Apr 8, 2026 •

Copy link
Copy Markdown
Member

Replace findOneBy(), findOne(), and getOne() with their OrFail variants across the test suite, addressing the most common SonarQube feedback — unnecessary non-null assertions. Related to #12255.

The OrFail methods throw EntityNotFoundError instead of returning null, which:

  • Removes the need for ! type assertions on every property access
  • Produces clearer error messages when a test fails (entity not found vs null property access)
  • Removes redundant expect(x).to.not.be.null checks (the OrFail method already guarantees non-null)

Also simplifies findOneOrFail({ where: {...} }) to findOneByOrFail({...}) and findOne({ where: {...} }) to findOneBy({...}) where no other options (relations, select, order) are used.

Tests that intentionally verify null results (e.g., after transaction rollbacks or inner joins with no matches) are left unchanged with findOne() / getOne().

Also fixes loadedfeature → loadedFeature camelCase naming in geometry column tests and replaces remaining non-null assertions on nullable properties with optional chaining.

@pkuczynski pkuczynski self-assigned this Apr 8, 2026
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1)   📘 Rule violations (0)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1)

Grey Divider


Action required

1. OrFail used for null 🐞 ≡
Description
query-builder-joins.test.ts switched multiple negative-path join queries from getOne() to
getOneOrFail() while still asserting loadedPost* is null, so these tests will throw
EntityNotFoundError instead of returning null.
Code

test/functional/query-builder/join/query-builder-joins.test.ts[R357-363]

            .createQueryBuilder(Post, "post")
            .innerJoinAndSelect("post.tag", "tag")
            .where("post.id = :id", { id: post.id })
-                        .getOne()
+                        .getOneOrFail()
-                    expect(loadedPost!).to.be.null
+                    expect(loadedPost).to.be.null
    }),
Evidence
Each highlighted test constructs a query that should return no rows and asserts the result is
null, but getOneOrFail() throws when no rows exist. This contradiction occurs in several places
in the same file.

test/functional/query-builder/join/query-builder-joins.test.ts[349-363]
test/functional/query-builder/join/query-builder-joins.test.ts[1068-1088]
test/functional/query-builder/join/query-builder-joins.test.ts[1345-1372]
src/query-builder/SelectQueryBuilder.ts[1810-1819]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Several negative-path join tests expect `getOne()` to return `null` when joins/conditions eliminate results, but the refactor changed them to `getOneOrFail()`, which throws.
### Issue Context
`getOneOrFail()` throws `EntityNotFoundError` when no entity is found; these tests are explicitly validating the "no result" behavior.
### Fix Focus Areas
- test/functional/query-builder/join/query-builder-joins.test.ts[349-364]
- test/functional/query-builder/join/query-builder-joins.test.ts[1068-1089]
- test/functional/query-builder/join/query-builder-joins.test.ts[1345-1373]
### Suggested change
For each test that asserts `expect(loadedPost...).to.be.null`, change the corresponding `.getOneOrFail()` back to `.getOne()` (or update the tests to expect a thrown error if the intent is to validate `getOneOrFail`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Rollback check uses OrFail ☑ 🐞 ≡
Description
test/functional/transaction/transaction-in-entity-manager/transaction-in-entity-manager.test.ts
verifies a failed transaction was rolled back by expecting post/category to be null, but it
now calls findOneOrFail() for both. This will throw EntityNotFoundError instead of returning
null, breaking the rollback verification.
Code

test/functional/transaction/transaction-in-entity-manager/transaction-in-entity-manager.test.ts[R114-125]

+                const post = await dataSource.manager.findOneOrFail(Post, {
    where: { title: "Post #1" },
})
expect(post).to.be.null
-                const category = await dataSource.manager.findOne(Category, {
-                    where: { name: "Category #1" },
-                })
+                const category = await dataSource.manager.findOneOrFail(
+                    Category,
+                    {
+                        where: { name: "Category #1" },
+                    },
+                )
expect(category).to.be.null
Evidence
After the transaction error is caught, the test expects both entities to be missing (to.be.null)
but uses findOneOrFail(), which rejects when the entity is not found (per the
EntityManager.findOneOrFail implementation).

test/functional/transaction/transaction-in-entity-manager/transaction-in-entity-manager.test.ts[114-125]
src/entity-manager/EntityManager.ts[1376-1387]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Rollback verification expects `null`, but `findOneOrFail()` throws when the record is missing.
### Issue Context
This test intentionally checks that after a failed transaction, persisted entities are rolled back and cannot be found.
### Fix Focus Areas
- test/functional/transaction/transaction-in-entity-manager/transaction-in-entity-manager.test.ts[114-125]
### Suggested fix
- Replace the rollback verification queries with `findOne()`/`findOneBy()` and keep `expect(x).to.be.null`.
- Or, keep `findOneOrFail()` and assert that the promise is rejected with `EntityNotFoundError` (and remove the `to.be.null` assertions).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Non-null assertions remain ☑ 🐞 ⚙
Description
findOneOrFail is used, but loadedPerson1! / loadedPerson2! non-null assertions are still
present in the single-table inheritance test, so this refactor doesn’t actually eliminate the
non-null-assertion pattern there. Since findOneOrFail rejects when the entity is missing, these
! assertions are redundant and likely continue triggering the same static-analysis feedback this
PR targets.
Code

test/functional/table-inheritance/single-table/basic-functionality/basic-functionality.test.ts[R438-454]

            ;(loadedPerson1! as Student).faculty.should.be.eql("Economics")
-                loadedPerson1!.should.not.haveOwnProperty("department")
-                loadedPerson1!.should.not.haveOwnProperty("specialization")
-                loadedPerson1!.should.not.haveOwnProperty("salary")
+                loadedPerson1.should.not.haveOwnProperty("department")
+                loadedPerson1.should.not.haveOwnProperty("specialization")
+                loadedPerson1.should.not.haveOwnProperty("salary")
-                const loadedPerson2 = await dataSource.manager.findOne(Person, {
-                    where: {
-                        id: 2,
+                const loadedPerson2 = await dataSource.manager.findOneOrFail(
+                    Person,
+                    {
+                        where: {
+                            id: 2,
+                        },
                },
-                })
-                loadedPerson2!.should.be.instanceof(Employee)
-                loadedPerson2!.id.should.be.eql(2)
-                loadedPerson2!.name.should.be.eql("John")
+                )
+                loadedPerson2.should.be.instanceof(Employee)
+                loadedPerson2.id.should.be.eql(2)
+                loadedPerson2.name.should.be.eql("John")
            ;(loadedPerson2! as Employee).salary.should.be.eql(1000)
-                loadedPerson2!.should.not.haveOwnProperty("department")
-                loadedPerson2!.should.not.haveOwnProperty("specialization")
-                loadedPerson2!.should.not.haveOwnProperty("faculty")
Evidence
The test uses findOneOrFail to load loadedPerson1/loadedPerson2, but still applies non-null
assertions when casting. In the codebase, EntityManager.findOneOrFail explicitly rejects when the
result is null, meaning the promise only resolves with a non-null entity, so the non-null
assertions are unnecessary.

test/functional/table-inheritance/single-table/basic-functionality/basic-functionality.test.ts[427-457]
src/entity-manager/EntityManager.ts[1376-1390]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `single-table/basic-functionality.test.ts`, `loadedPerson1` and `loadedPerson2` are loaded via `findOneOrFail` but are still cast using `loadedPerson1! as Student` / `loadedPerson2! as Employee`. Because `findOneOrFail` already guarantees non-null (or throws), these `!` operators are redundant and likely keep the Sonar warning this PR aims to remove.
### Issue Context
`EntityManager.findOneOrFail` rejects when the fetched value is `null`, so the variable can be treated as non-null without an assertion.
### Fix Focus Areas
- test/functional/table-inheritance/single-table/basic-functionality/basic-functionality.test.ts[427-457]
- src/entity-manager/EntityManager.ts[1376-1390]
### Suggested change
Replace:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. loadedfeature not camelCase ☑ 📘 ⚙
Description
The updated test code uses identifiers like loadedfeature/updatedfeature, which deviate from the
project’s typical camelCase naming (e.g., loadedFeature). This reduces readability and violates
the naming-conventions requirement.
Code

test/core/column-kinds/geometry-column/geometry-column.test.ts[R35-41]

+                const loadedfeature = await featureRepository.findOneByOrFail({
            id: feature.id,
        })
-                expect(loadedfeature).to.be.not.empty
-                expect(loadedfeature!.name).to.be.eql("feature")
-                expect(loadedfeature!.shape).to.be.eql(
+                expect(loadedfeature.name).to.be.eql("feature")
+                expect(loadedfeature.shape).to.be.eql(
            "POINT (828365.16700000037 823377.14699999988)",
        )
Evidence
PR Compliance ID 1 requires consistent naming conventions for identifiers. The changed lines include
loadedfeature (and similarly updatedfeature later in the file), which is inconsistent with
standard camelCase naming.

Rule 1: Consistent Naming Conventions
test/core/column-kinds/geometry-column/geometry-column.test.ts[35-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The identifiers `loadedfeature` and `updatedfeature` do not follow camelCase naming (expected `loadedFeature` / `updatedFeature`).
## Issue Context
This PR touches these lines while refactoring `findOneBy()` to `findOneByOrFail()`, so it’s a good opportunity to align the variable names with project conventions.
## Fix Focus Areas
- test/core/column-kinds/geometry-column/geometry-column.test.ts[35-76]
- test/core/column-kinds/geometry-column/geometry-column.test.ts[93-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 77db4e7

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 7baa9a0

@OSA413

OSA413 commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Is there some original variants left in the tests? Do we test them separately actually somewhere? Like, does the fail varian use the original variant under the hood? If so, we need to assert that.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit f56ffb3

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 9aa1f35

@coveralls

coveralls commented Apr 8, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 73.662% (-0.03%) from 73.692% — refactor/tests-use-find-or-fail into master

@pkuczynski

Copy link
Copy Markdown
Member Author

Is there some original variants left in the tests? Do we test them separately actually somewhere? Like, does the fail varian use the original variant under the hood? If so, we need to assert that.

Sorry @OSA413 but I do not understand the question. Are you asking if fineOneOrFail is using findOne under the hood? Then asnwer is yes: https://github.com/typeorm/typeorm/blob/master/src/entity-manager/EntityManager.ts#L1376

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 3b72167

@OSA413

OSA413 commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

yes, do we have a test that checks correlation between fineOneOrFail and fineOne? (or, preferably, a spy with call count)

@pkuczynski

pkuczynski commented Apr 8, 2026 •

Copy link
Copy Markdown
Member Author

yes, do we have a test that checks correlation between fineOneOrFail and fineOne? (or, preferably, a spy with call count)

I don’t think we do, at least not by quick search. This would be a candidate for unit test, however it is hard to imagine this would be implemented in any other way then calling findX and checking results... @alumni do you think it’s worth to add this extra unit test for all OrFail methods?

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 3860eb0

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit bb6302c

@pkuczynski
pkuczynski enabled auto-merge (squash) April 8, 2026 19:25
@sonarqubecloud

sonarqubecloud Bot commented Apr 8, 2026

Copy link
Copy Markdown

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit a32636f

@pkuczynski
pkuczynski merged commit 876aed9 into master Apr 8, 2026
42 checks passed
@pkuczynski
pkuczynski deleted the refactor/tests-use-find-or-fail branch April 8, 2026 19:39
@github-actions github-actions Bot added this to the 1.0 milestone Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants