Bug 638446: Refactor: Split SubcSubcontractingTest into 3 focused codeunits#8225
Bug 638446: Refactor: Split SubcSubcontractingTest into 3 focused codeunits#8225ChethanT wants to merge 6 commits into
Conversation
Split the monolithic test codeunit (139989, 45 tests, 3816 lines) into three focused codeunits: - Subc. Transfer Order Test (139993): 14 tests covering transfer order creation, location handling, returns, reservations, bin codes, direct transfer - Subc. Purchase Order Test (139994): 15 tests covering PO creation from routing, posting groups, calculate subcontracts, descriptions, pricing - Subc. Subcontracting Test (139989): 16 remaining misc tests (setup, factbox, planning, copy document, dispatching, component supply method) Each new codeunit is self-contained with required handlers and helpers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8f263c8 to
646987d
Compare
…tor/Subcontracting/SplitTestCodeunit
…tor/Subcontracting/SplitTestCodeunit
| var | ||
| ReservationEntry: Record "Reservation Entry"; | ||
| begin | ||
| ReservationEntry.DeleteAll(); |
There was a problem hiding this comment.
Unfiltered DeleteAll wipes all Reservation Entries
ReservationEntry.DeleteAll() is called without any SetRange filter inside MockReservationEntryOnTransferLine, which deletes every reservation entry in the database, not just those belonging to the transfer line under test.
Recommendation:
- Add SetRange filters scoped to the transfer line before calling DeleteAll so only the relevant reservation entries are removed.
| ReservationEntry.DeleteAll(); | |
| ReservationEntry.SetRange("Source Type", Database::"Transfer Line"); | |
| ReservationEntry.SetRange("Source ID", TransferLine."Document No."); | |
| ReservationEntry.SetRange("Source Ref. No.", TransferLine."Line No."); | |
| ReservationEntry.DeleteAll(); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order); | ||
| PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No."); | ||
| PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No."); | ||
| Assert.AreEqual(false, PurchaseLine.IsEmpty(), ''); |
There was a problem hiding this comment.
Assert.AreEqual(false, ...) instead of Assert.IsFalse
Using Assert.AreEqual(false, PurchaseLine.IsEmpty(), '') is semantically correct but harder to read and produces a less descriptive failure message than the idiomatic form.
Recommendation:
- Replace with
Assert.IsFalseand add a meaningful message string.
| Assert.AreEqual(false, PurchaseLine.IsEmpty(), ''); | |
| Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase Line should exist for the production order.'); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order); | ||
| PurchaseLine.SetRange(Type, PurchaseLine.Type::Item); | ||
| PurchaseLine.SetRange("No.", ProductionBOMLine."No."); | ||
| Assert.AreEqual(false, PurchaseLine.IsEmpty(), ''); |
There was a problem hiding this comment.
Assert.AreEqual(false, ...) instead of Assert.IsFalse
Using Assert.AreEqual(false, PurchaseLine.IsEmpty(), '') with an empty message makes failures impossible to diagnose from the test output alone.
Recommendation:
- Replace with
Assert.IsFalseand supply a meaningful failure message.
| Assert.AreEqual(false, PurchaseLine.IsEmpty(), ''); | |
| Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase Line should exist for the production BOM component.'); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Vendor."Subc. Location Code" := Location.Code; | ||
| LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location); | ||
| Vendor."Location Code" := Location.Code; | ||
| Vendor.Modify(); |
There was a problem hiding this comment.
Vendor.Modify() without trigger in test setup
Vendor.Modify() (without true) is used after directly assigning "Subc. Location Code" and "Location Code", skipping any OnModify event subscribers that synchronize related records.
Recommendation:
- Use
Vendor.Modify(true)to ensure all trigger-driven side effects are applied.
| Vendor.Modify(); | |
| Vendor.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // [GIVEN] Create Subcontracting Purchase Order from Prod. Order Routing | ||
| WorkCenter2 := WorkCenter[2]; | ||
| WorkCenter2."Subcontractor No." := Vendor."No."; | ||
| WorkCenter2.Modify(); |
There was a problem hiding this comment.
WorkCenter2.Modify() without trigger skips OnModify
WorkCenter2."Subcontractor No." := Vendor."No." followed by WorkCenter2.Modify() (without true) does not fire OnModify on the Work Center, potentially missing subcontracting price or linked record updates.
Recommendation:
- Use
WorkCenter2.Modify(true)so OnModify event subscribers execute.
| WorkCenter2.Modify(); | |
| WorkCenter2.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ProdOrderComp.FindFirst(); | ||
| ExpectedDescription2 := 'TestDescription2_Comp'; | ||
| ProdOrderComp."Description 2" := ExpectedDescription2; | ||
| ProdOrderComp.Modify(); |
There was a problem hiding this comment.
Direct field set and Modify() bypasses OnModify triggers
ProdOrderComp."Description 2" := ... followed by ProdOrderComp.Modify() (without true) writes to the database skipping OnModify, which may miss trigger-driven field propagation relevant to the test scenario.
Recommendation:
- Use
ProdOrderComp.Modify(true)so that any OnModify logic runs consistently.
| ProdOrderComp.Modify(); | |
| ProdOrderComp.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Vendor.Modify(); | ||
| end; | ||
|
|
||
| procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; |
There was a problem hiding this comment.
Public helper procedure should be local
procedure CreateWorkCenter(...) is declared without local, making it part of the codeunit's public surface. Test helper procedures should be local to prevent unintended external callers and to clearly express encapsulation intent.
Recommendation:
- Add the
localkeyword to this procedure declaration.
| procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; | |
| local procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
|
|
||
| if Subcontract then begin | ||
| LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT"); | ||
| GenProductPostingGroup.FindFirst(); |
There was a problem hiding this comment.
FindFirst without filter on GenProductPostingGroup
GenProductPostingGroup.FindFirst() is called inside CreateWorkCenter with no SetRange filter, making the selected posting group non-deterministic and potentially causing tests to silently share or pollute posting group setup across test runs.
Recommendation:
- Use a specific filter or
LibraryERM.FindGenProductPostingGroupto select a predictable posting group, or create a dedicated one for the test.
| GenProductPostingGroup.FindFirst(); | |
| LibraryERM.FindGenProductPostingGroup(GenProductPostingGroup); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ManufacturingSetup.Modify(); | ||
| end; | ||
|
|
||
| procedure SelectRequisitionTemplateName(): Code[10] |
There was a problem hiding this comment.
Public helper procedure should be local
procedure SelectRequisitionTemplateName() has no local qualifier in a test codeunit, exposing an internal helper as part of the public API without any test or handler purpose.
Recommendation:
- Declare it as
local procedure SelectRequisitionTemplateName().
| procedure SelectRequisitionTemplateName(): Code[10] | |
| local procedure SelectRequisitionTemplateName(): Code[10] |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder( | ||
| ProductionOrder2, "Production Order Status"::Released, ProductionOrder2."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5); | ||
| SetAllProdOrderTransferComponentLocations(ProductionOrder2."No.", ProductionLocation.Code); |
There was a problem hiding this comment.
Modify() without trigger after Validate in test setup
ProdOrderComp.Validate("Component Supply Method", ...) updates the record in memory, then ProdOrderComp.Modify() (without true) persists it without running the OnModify trigger. If the OnModify trigger performs location or state updates, those are silently skipped.
Recommendation:
- Use
ProdOrderComp.Modify(true)to also run OnModify triggers, ensuring consistent state.
| SetAllProdOrderTransferComponentLocations(ProductionOrder2."No.", ProductionLocation.Code); | |
| ProdOrderComp.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ProductionOrder: Record "Production Order"; | ||
| PurchaseHeader: Record "Purchase Header"; | ||
| PurchaseLine: Record "Purchase Line"; | ||
| TransferHeader: Record "Transfer Header"; |
There was a problem hiding this comment.
Empty assertion message hides failure context
Assert.AreEqual(SubPurchaseLineFactbox.SubcontractingPrices.Value, Format(SubcontractorPrice.Count()), '') uses an empty failure message, so any assertion failure in test output provides no context about what was expected.
Recommendation:
- Add a descriptive message and consider swapping expected/actual order so expected is first.
| TransferHeader: Record "Transfer Header"; | |
| Assert.AreEqual(Format(SubcontractorPrice.Count()), SubPurchaseLineFactbox.SubcontractingPrices.Value, 'Subcontracting Prices factbox count must match the number of Subcontractor Price records.'); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| #pragma warning restore AA0210 | ||
| ProdOrderComp.FindFirst(); | ||
| ProdOrderComp."Location Code" := Location.Code; | ||
| ProdOrderComp.Modify(); |
There was a problem hiding this comment.
Direct field set and Modify() bypasses OnModify triggers
ProdOrderComp."Location Code" := Location.Code followed by ProdOrderComp.Modify() (no true) skips OnModify triggers. If those triggers adjust linked transfer lines or planning data, the test will operate on inconsistent state.
Recommendation:
- Use
ProdOrderComp.Modify(true)to execute OnModify triggers.
| ProdOrderComp.Modify(); | |
| ProdOrderComp.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| #pragma warning restore AA0210 | ||
| ProdOrderComp.FindFirst(); | ||
|
|
||
| Assert.AreEqual(ProdOrderComp."Location Code", LocationCode, ''); |
There was a problem hiding this comment.
Empty assertion messages in location/bin checks
Assert.AreEqual(ProdOrderComp."Location Code", LocationCode, '') and the following BinCode assertion both have empty messages, making it impossible to distinguish which field failed in a test failure report.
Recommendation:
- Add descriptive failure messages to both assertions.
| Assert.AreEqual(ProdOrderComp."Location Code", LocationCode, ''); | |
| Assert.AreEqual(ProdOrderComp."Location Code", LocationCode, 'Prod. Order Component Location Code must match the expected location.'); | |
| Assert.AreEqual(ProdOrderComp."Bin Code", BinCode, 'Prod. Order Component Bin Code must match the expected bin.'); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| LibraryWarehouse.CreateBin(Bin, Location.Code, '', '', ''); | ||
| ProdOrderComp."Location Code" := Location.Code; | ||
| ProdOrderComp."Bin Code" := Bin.Code; | ||
| ProdOrderComp.Modify(); |
There was a problem hiding this comment.
Direct field set and Modify() bypasses OnModify triggers
Setting ProdOrderComp."Location Code" and "Bin Code" directly then calling Modify() (without true) skips OnModify processing. Subcontracting OnModify logic that reacts to location changes will not fire.
Recommendation:
- Use
ProdOrderComp.Modify(true)here and at the similar call on line 1478.
| ProdOrderComp.Modify(); | |
| ProdOrderComp.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Vendor."Subc. Location Code" := Location.Code; | ||
| LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location); | ||
| Vendor."Location Code" := Location.Code; | ||
| Vendor.Modify(); |
There was a problem hiding this comment.
Vendor.Modify() without trigger skips OnModify
Vendor."Subc. Location Code" is set directly then saved with Vendor.Modify() (no true), bypassing any OnModify event subscribers that might propagate location information to subcontracting records.
Recommendation:
- Use
Vendor.Modify(true)to execute OnModify triggers.
| Vendor.Modify(); | |
| Vendor.Modify(true); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Vendor.Modify(); | ||
| end; | ||
|
|
||
| procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; |
There was a problem hiding this comment.
Public helper procedure should be local
procedure CreateWorkCenter(...) is declared without local in a test codeunit, unnecessarily exposing a setup helper as a public method.
Recommendation:
- Add
localto the procedure declaration.
| procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; | |
| local procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
|
|
||
| if Subcontract then begin | ||
| LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT"); | ||
| GenProductPostingGroup.FindFirst(); |
There was a problem hiding this comment.
FindFirst without filter on GenProductPostingGroup
The CreateWorkCenter helper calls GenProductPostingGroup.FindFirst() with no filter, producing non-deterministic test behaviour — whichever posting group happens to be first may not have the VAT setup required by the test.
Recommendation:
- Replace with a filtered or library-assisted lookup so the same posting group is selected reliably across environments.
| GenProductPostingGroup.FindFirst(); | |
| LibraryERM.FindGenProductPostingGroup(GenProductPostingGroup); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ReservationEntry.Insert(); | ||
| end; | ||
|
|
||
| procedure CreateInventory(Item: Record Item; Location: Record Location; Bin: Record Bin; Quantity: Decimal) |
There was a problem hiding this comment.
Public helper procedure should be local
procedure CreateInventory(...) is not local, exposing an inventory setup helper as a public method from a test codeunit with no need for external access.
Recommendation:
- Add
localto the procedure declaration.
| procedure CreateInventory(Item: Record Item; Location: Record Location; Bin: Record Bin; Quantity: Decimal) | |
| local procedure CreateInventory(Item: Record Item; Location: Record Location; Bin: Record Bin; Quantity: Decimal) |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ProductionOrder: Record "Production Order"; | ||
| PurchaseHeader: Record "Purchase Header"; | ||
| PurchaseLine: Record "Purchase Line"; | ||
| TransferHeader: Record "Transfer Header"; |
There was a problem hiding this comment.
Factbox count uses full-table Count()
The assertion compares the factbox value for one purchase line against SubcontractorPrice.Count() with no filters. That performs a full scan and becomes nondeterministic if any other subcontractor prices exist in the test company.
Recommendation:
- Apply the same item/work center/vendor filters used by the UI context before calling
Count().
| TransferHeader: Record "Transfer Header"; | |
| SubcontractorPrice.SetRange("Work Center No.", WorkCenter[2]."No."); | |
| SubcontractorPrice.SetRange("Item No.", Item."No."); | |
| Assert.AreEqual(SubPurchaseLineFactbox.SubcontractingPrices.Value, Format(SubcontractorPrice.Count()), ''); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| PurchCommentLine.SetRange("No.", PurchaseLine."Document No."); | ||
| PurchCommentLine.SetRange("Document Line No.", PurchaseLine."Line No."); | ||
|
|
||
| Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase Comment Line must be filled'); |
There was a problem hiding this comment.
Comment-line assertion checks wrong record
The test filters Purch. Comment Line but asserts on PurchaseLine.IsEmpty() instead. That assertion is unrelated to the transferred comment and lets the test pass even when no comment line was created.
Recommendation:
- Assert on
PurchCommentLineafter applying the filters so the test actually validates comment transfer.
| Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase Comment Line must be filled'); | |
| Assert.IsFalse(PurchCommentLine.IsEmpty(), 'Purchase Comment Line must be filled'); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| WorkCenter[2].Modify(true); | ||
|
|
||
| // [GIVEN] Create requisition worksheet | ||
| ReqWkshTemplate.DeleteAll(true); |
There was a problem hiding this comment.
Test removes all requisition templates
ReqWkshTemplate.DeleteAll(true) clears every requisition worksheet template, not just data created by this test. That can invalidate shared setup and make unrelated tests fail unpredictably.
Recommendation:
- Create or reuse a dedicated template for this test instead of deleting the whole table, or at minimum filter deletion to a test-specific template name.
| ReqWkshTemplate.DeleteAll(true); | |
| if not ReqWkshTemplate.Get(SelectRequisitionTemplateName()) then begin | |
| ReqWkshTemplate.Init(); | |
| ReqWkshTemplate.Validate(Name, SelectRequisitionTemplateName()); | |
| ReqWkshTemplate.Insert(true); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| var | ||
| ReservationEntry: Record "Reservation Entry"; | ||
| begin | ||
| ReservationEntry.DeleteAll(); |
There was a problem hiding this comment.
Mock helper deletes all reservation entries
Calling ReservationEntry.DeleteAll() without filters wipes the entire table for the company. This breaks test isolation and can corrupt other transfer, purchase, or production tests running in the same suite.
Recommendation:
- Delete only reservation entries that belong to the specific transfer line being mocked.
| ReservationEntry.DeleteAll(); | |
| ReservationEntry.SetRange("Source Type", Database::"Transfer Line"); | |
| ReservationEntry.SetRange("Source ID", TransferLine."Document No."); | |
| ReservationEntry.SetRange("Source Ref. No.", TransferLine."Line No."); | |
| ReservationEntry.DeleteAll(); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| GenProductPostingGroup.Get(WorkCenter[2]."Gen. Prod. Posting Group"); | ||
| OriginalDefVATProdPostGrp := GenProductPostingGroup."Def. VAT Prod. Posting Group"; | ||
| GenProductPostingGroup."Def. VAT Prod. Posting Group" := ''; | ||
| GenProductPostingGroup.Modify(); |
There was a problem hiding this comment.
Shared setup is modified without safe restore
The test persists a blank Def. VAT Prod. Posting Group on a live posting group and only restores it at the end. If any assertion or runtime error occurs before teardown, later tests inherit the broken setup.
Recommendation:
- Avoid mutating shared setup records directly; use a test-specific posting group or a save/restore helper that guarantees cleanup even on failure.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Summary
Splits the monolithic test codeunit \Subc. Subcontracting Test\ (139989, 42 tests, 3562 lines) into three focused codeunits for better maintainability:
New Codeunits
Motivation
The original codeunit was growing unbounded due to its generic name. This split groups tests by feature area while avoiding over-fragmentation.
Each new codeunit is self-contained with required handlers and helper procedures.
Fixes AB#638446