{"id":2086,"date":"2025-11-17T21:37:26","date_gmt":"2025-11-18T05:37:26","guid":{"rendered":"https:\/\/wp.c9h.org\/cj\/?p=2086"},"modified":"2025-11-21T15:38:19","modified_gmt":"2025-11-21T23:38:19","slug":"navigating-the-perils-of-perl-mocking-a-debugging-journey","status":"publish","type":"post","link":"https:\/\/wp.c9h.org\/cj\/?p=2086","title":{"rendered":"Navigating the Perils of Perl Mocking: A Debugging Journey"},"content":{"rendered":"<p>As a software engineer, I often find myself deep in the trenches of<br \/>\ntesting, ensuring the code I write is not just functional but also<br \/>\nresilient. Recently, I embarked on a refactoring project that involved<br \/>\nmoving a script\u2019s functionality into a new Perl module,<br \/>\n<code>App::Workflow::Controller<\/code>. This module\u2019s job was to<br \/>\norchestrate a few tasks: query a database via an external CLI tool,<br \/>\nfetch more details based on the results, and write outputs. This<br \/>\nnaturally called for mocking external dependencies in the unit tests,<br \/>\nand that\u2019s where the real fun began.<\/p>\n<p>My go-to tool for this is usually <a\nhref=\"https:\/\/metacpan.org\/pod\/Test::MockModule\">Test::MockModule<\/a>.<br \/>\nThe module under test, <code>App::Workflow::Controller<\/code>, used:<\/p>\n<ul>\n<li><a href=\"https:\/\/metacpan.org\/pod\/IPC::Run\">IPC::Run<\/a> to call the<br \/>\nexternal CLI tool.<\/li>\n<li><a href=\"https:\/\/metacpan.org\/pod\/File::ShareDir\">File::ShareDir<\/a><br \/>\nto locate SQL files.<\/li>\n<li>Internal modules like <code>App::Data::Fetcher<\/code> and<br \/>\n<code>App::Utils<\/code>.<\/li>\n<\/ul>\n<h2\nid=\"understanding-the-mocking-challenge-exported-functions\">Understanding<br \/>\nthe Mocking Challenge: Exported Functions<\/h2>\n<p>The core challenge revolves around how Perl handles imported<br \/>\nfunctions. When a module like <code>App::Workflow::Controller<\/code><br \/>\nuses <code>use IPC::Run qw(run);<\/code>, it copies the <code>run<\/code><br \/>\nsubroutine into its own namespace <em>at compile time<\/em>.<\/p>\n<p><strong>Key Insight 1: Mock in the Calling Namespace<\/strong><\/p>\n<p>As the <a\nhref=\"https:\/\/metacpan.org\/pod\/Test::MockModule#MOCKING-EXPORT\">Test::MockModule<br \/>\ndocumentation<\/a> clearly states, to affect the <code>run<\/code> used<br \/>\n<em>by<\/em> <code>App::Workflow::Controller<\/code>, you must mock<br \/>\n<code>App::Workflow::Controller::run<\/code>. Mocking<br \/>\n<code>IPC::Run::run<\/code> after <code>App::Workflow::Controller<\/code><br \/>\nis loaded has no effect on the already imported symbol.<\/p>\n<div class=\"sourceCode\" id=\"cb1\">\n<pre\nclass=\"sourceCode perl\"><code class=\"sourceCode perl\"><span id=\"cb1-1\"><a href=\"#cb1-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># t\/controller.t<\/span><\/span>\n<span id=\"cb1-2\"><a href=\"#cb1-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::More<\/span>;<\/span>\n<span id=\"cb1-3\"><a href=\"#cb1-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::MockModule<\/span>;<\/span>\n<span id=\"cb1-4\"><a href=\"#cb1-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">App::Workflow<\/span>::<span class=\"fu\">Controller<\/span>; <span class=\"co\"># Load the module first<\/span><\/span>\n<span id=\"cb1-5\"><a href=\"#cb1-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb1-6\"><a href=\"#cb1-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># Mock run within App::Workflow::Controller&#39;s namespace<\/span><\/span>\n<span id=\"cb1-7\"><a href=\"#cb1-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">my<\/span> <span class=\"dt\">$runner_mock<\/span> = <span class=\"fu\">Test::MockModule<\/span>-&gt;new(<span class=\"ot\">&#39;<\/span><span class=\"ss\">App::Workflow::Controller<\/span><span class=\"ot\">&#39;<\/span>, no_auto =&gt; <span class=\"dv\">1<\/span>);<\/span>\n<span id=\"cb1-8\"><a href=\"#cb1-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"dt\">$runner_mock<\/span>-&gt;<span class=\"dt\">mock<\/span>(<span class=\"ot\">&#39;<\/span><span class=\"ss\">run<\/span><span class=\"ot\">&#39;<\/span>, <span class=\"kw\">sub <\/span>{<\/span>\n<span id=\"cb1-9\"><a href=\"#cb1-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> (<span class=\"dt\">$cmd_array_ref<\/span>, <span class=\"dt\">@redirections<\/span>) = <span class=\"dt\">@_<\/span>;<\/span>\n<span id=\"cb1-10\"><a href=\"#cb1-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\"># Mocked logic to handle the command and return expected results<\/span><\/span>\n<span id=\"cb1-11\"><a href=\"#cb1-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\"># Example:<\/span><\/span>\n<span id=\"cb1-12\"><a href=\"#cb1-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">if<\/span> (<span class=\"dt\">$cmd_array_ref<\/span>-&gt;[<span class=\"dv\">0<\/span>] <span class=\"ot\">eq<\/span> <span class=\"ot\">&#39;<\/span><span class=\"ss\">\/usr\/bin\/my_cli_tool<\/span><span class=\"ot\">&#39;<\/span>) {<\/span>\n<span id=\"cb1-13\"><a href=\"#cb1-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"kw\">my<\/span> <span class=\"dt\">$out_ref<\/span> = <span class=\"dt\">$redirections<\/span>[<span class=\"dv\">1<\/span>]; <span class=\"co\"># Assuming &#39;&gt;&#39;<\/span><\/span>\n<span id=\"cb1-14\"><a href=\"#cb1-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"dt\">$$out_ref<\/span> = <span class=\"ot\">&quot;<\/span><span class=\"st\">mocked output<\/span><span class=\"ot\">&quot;<\/span>;<\/span>\n<span id=\"cb1-15\"><a href=\"#cb1-15\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"kw\">return<\/span> <span class=\"dv\">1<\/span>; <span class=\"co\"># Simulate success<\/span><\/span>\n<span id=\"cb1-16\"><a href=\"#cb1-16\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    }<\/span>\n<span id=\"cb1-17\"><a href=\"#cb1-17\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"fu\">die<\/span> <span class=\"ot\">&quot;<\/span><span class=\"st\">Unexpected command<\/span><span class=\"ot\">&quot;<\/span>;<\/span>\n<span id=\"cb1-18\"><a href=\"#cb1-18\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>});<\/span>\n<span id=\"cb1-19\"><a href=\"#cb1-19\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb1-20\"><a href=\"#cb1-20\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># ... tests calling methods of App::Workflow::Controller that use run ...<\/span><\/span><\/code><\/pre>\n<\/div>\n<p><strong>Key Insight 2: Timing Matters &#8211; BEGIN Blocks<\/strong><\/p>\n<p>For dependencies used during the <em>compilation<\/em> of<br \/>\n<code>App::Workflow::Controller<\/code> (e.g., in attribute defaults or<br \/>\nbuilders using <code>File::ShareDir<\/code>), the mocks <em>must<\/em> be<br \/>\nactive <em>before<\/em> <code>use App::Workflow::Controller;<\/code> is<br \/>\nexecuted. This requires placing the <code>Test::MockModule<\/code> setup<br \/>\nfor those specific dependencies inside a <code>BEGIN<\/code> block.<\/p>\n<div class=\"sourceCode\" id=\"cb2\">\n<pre\nclass=\"sourceCode perl\"><code class=\"sourceCode perl\"><span id=\"cb2-1\"><a href=\"#cb2-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># t\/controller.t<\/span><\/span>\n<span id=\"cb2-2\"><a href=\"#cb2-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"kw\">strict<\/span>;<\/span>\n<span id=\"cb2-3\"><a href=\"#cb2-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"kw\">warnings<\/span>;<\/span>\n<span id=\"cb2-4\"><a href=\"#cb2-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::More<\/span>;<\/span>\n<span id=\"cb2-5\"><a href=\"#cb2-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::MockModule<\/span>;<\/span>\n<span id=\"cb2-6\"><a href=\"#cb2-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># ... other includes ...<\/span><\/span>\n<span id=\"cb2-7\"><a href=\"#cb2-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb2-8\"><a href=\"#cb2-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">my<\/span> <span class=\"dt\">$mock_sql_file_path<\/span> = <span class=\"ot\">&#39;<\/span><span class=\"ss\">...<\/span><span class=\"ot\">&#39;<\/span>; <span class=\"co\"># Path to mock SQL<\/span><\/span>\n<span id=\"cb2-9\"><a href=\"#cb2-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb2-10\"><a href=\"#cb2-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">BEGIN<\/span> {<\/span>\n<span id=\"cb2-11\"><a href=\"#cb2-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\"># Mock File::ShareDir BEFORE App::Workflow::Controller is loaded<\/span><\/span>\n<span id=\"cb2-12\"><a href=\"#cb2-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> <span class=\"dt\">$fsd_mock<\/span> = <span class=\"fu\">Test::MockModule<\/span>-&gt;new(<span class=\"ot\">&#39;<\/span><span class=\"ss\">File::ShareDir<\/span><span class=\"ot\">&#39;<\/span>);<\/span>\n<span id=\"cb2-13\"><a href=\"#cb2-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"dt\">$fsd_mock<\/span>-&gt;<span class=\"dt\">redefine<\/span>(<span class=\"ot\">&#39;<\/span><span class=\"ss\">dist_file<\/span><span class=\"ot\">&#39;<\/span>, <span class=\"kw\">sub <\/span>{ <span class=\"kw\">return<\/span> <span class=\"dt\">$mock_sql_file_path<\/span>; });<\/span>\n<span id=\"cb2-14\"><a href=\"#cb2-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>}<\/span>\n<span id=\"cb2-15\"><a href=\"#cb2-15\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb2-16\"><a href=\"#cb2-16\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">App::Workflow<\/span>::<span class=\"fu\">Controller<\/span>;<\/span>\n<span id=\"cb2-17\"><a href=\"#cb2-17\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb2-18\"><a href=\"#cb2-18\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># ... Mock IPC::Run as shown above ...<\/span><\/span><\/code><\/pre>\n<\/div>\n<h2 id=\"the-ipcrun-enigma-self-corruption\">The IPC::Run Enigma: $self<br \/>\nCorruption<\/h2>\n<p>In this specific journey, despite correctly mocking<br \/>\n<code>App::Workflow::Controller::run<\/code>, I encountered bizarre<br \/>\nerrors where the <code>$self<\/code> object within my module\u2019s methods<br \/>\nseemed to be corrupted, sometimes appearing as the command path. This<br \/>\nindicated a deep, complex interaction between<br \/>\n<code>Test::MockModule<\/code>\u2019s override mechanism and the way<br \/>\n<code>IPC::Run<\/code> manipulates the call stack or environment.<\/p>\n<p>After extensive debugging, it seems that directly mocking the<br \/>\nimported <code>run<\/code> sub from <code>IPC::Run<\/code> can be<br \/>\nunstable, potentially due to the intricacies of how<br \/>\n<code>IPC::Run<\/code> works internally.<\/p>\n<h2 id=\"the-pragmatic-solution-mocking-an-internal-wrapper\">The<br \/>\nPragmatic Solution: Mocking an Internal Wrapper<\/h2>\n<p>The most robust solution was to avoid mocking the imported<br \/>\n<code>run<\/code> directly. Instead, I refactored<br \/>\n<code>App::Workflow::Controller<\/code> to have a small, internal method<br \/>\nthat wraps the call to <code>IPC::Run::run<\/code>:<\/p>\n<div class=\"sourceCode\" id=\"cb3\">\n<pre\nclass=\"sourceCode perl\"><code class=\"sourceCode perl\"><span id=\"cb3-1\"><a href=\"#cb3-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># In App::Workflow::Controller.pm<\/span><\/span>\n<span id=\"cb3-2\"><a href=\"#cb3-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">sub <\/span><span class=\"fu\">_execute_cli<\/span> {<\/span>\n<span id=\"cb3-3\"><a href=\"#cb3-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> (<span class=\"dt\">$self<\/span>, <span class=\"dt\">@cmd_list<\/span>) = <span class=\"dt\">@_<\/span>;<\/span>\n<span id=\"cb3-4\"><a href=\"#cb3-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> <span class=\"dt\">$stdout<\/span>;<\/span>\n<span id=\"cb3-5\"><a href=\"#cb3-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    run \\<span class=\"dt\">@cmd_list<\/span>, <span class=\"ot\">&#39;<\/span><span class=\"ss\">&gt;<\/span><span class=\"ot\">&#39;<\/span>, \\<span class=\"dt\">$stdout<\/span> <span class=\"ot\">or<\/span> <span class=\"fu\">die<\/span> <span class=\"ot\">&quot;<\/span><span class=\"st\">Failed<\/span><span class=\"ot\">&quot;<\/span>;<\/span>\n<span id=\"cb3-6\"><a href=\"#cb3-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">return<\/span> <span class=\"dt\">$stdout<\/span>;<\/span>\n<span id=\"cb3-7\"><a href=\"#cb3-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>}<\/span>\n<span id=\"cb3-8\"><a href=\"#cb3-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb3-9\"><a href=\"#cb3-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">sub <\/span><span class=\"fu\">some_other_method<\/span> {<\/span>\n<span id=\"cb3-10\"><a href=\"#cb3-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> <span class=\"dt\">$self<\/span> = <span class=\"fu\">shift<\/span>;<\/span>\n<span id=\"cb3-11\"><a href=\"#cb3-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\"># ...<\/span><\/span>\n<span id=\"cb3-12\"><a href=\"#cb3-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> <span class=\"dt\">$output<\/span> = <span class=\"dt\">$self<\/span>-&gt;<span class=\"dt\">_execute_cli<\/span>(<span class=\"ot\">&#39;<\/span><span class=\"ss\">\/usr\/bin\/my_cli_tool<\/span><span class=\"ot\">&#39;<\/span>, <span class=\"ot\">&#39;<\/span><span class=\"ss\">arg1<\/span><span class=\"ot\">&#39;<\/span>);<\/span>\n<span id=\"cb3-13\"><a href=\"#cb3-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"co\"># ...<\/span><\/span>\n<span id=\"cb3-14\"><a href=\"#cb3-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>}<\/span><\/code><\/pre>\n<\/div>\n<p>Then, in the test, I mock <code>_execute_cli<\/code>:<\/p>\n<div class=\"sourceCode\" id=\"cb4\">\n<pre\nclass=\"sourceCode perl\"><code class=\"sourceCode perl\"><span id=\"cb4-1\"><a href=\"#cb4-1\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"co\"># t\/controller.t<\/span><\/span>\n<span id=\"cb4-2\"><a href=\"#cb4-2\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::More<\/span>;<\/span>\n<span id=\"cb4-3\"><a href=\"#cb4-3\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">Test::MockModule<\/span>;<\/span>\n<span id=\"cb4-4\"><a href=\"#cb4-4\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"fu\">use<\/span> <span class=\"fu\">App::Workflow<\/span>::<span class=\"fu\">Controller<\/span>;<\/span>\n<span id=\"cb4-5\"><a href=\"#cb4-5\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb4-6\"><a href=\"#cb4-6\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><span class=\"kw\">my<\/span> <span class=\"dt\">$controller_mock<\/span> = <span class=\"fu\">Test::MockModule<\/span>-&gt;new(<span class=\"ot\">&#39;<\/span><span class=\"ss\">App::Workflow::Controller<\/span><span class=\"ot\">&#39;<\/span>);<\/span>\n<span id=\"cb4-7\"><a href=\"#cb4-7\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb4-8\"><a href=\"#cb4-8\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>subtest <span class=\"ot\">&#39;<\/span><span class=\"ss\">Successful run<\/span><span class=\"ot\">&#39;<\/span> =&gt; <span class=\"kw\">sub <\/span>{<\/span>\n<span id=\"cb4-9\"><a href=\"#cb4-9\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"dt\">$controller_mock<\/span>-&gt;<span class=\"dt\">redefine<\/span>(<span class=\"ot\">&#39;<\/span><span class=\"ss\">_execute_cli<\/span><span class=\"ot\">&#39;<\/span>, <span class=\"kw\">sub <\/span>{<\/span>\n<span id=\"cb4-10\"><a href=\"#cb4-10\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"kw\">my<\/span> (<span class=\"dt\">$self<\/span>, <span class=\"dt\">@cmd<\/span>) = <span class=\"dt\">@_<\/span>;<\/span>\n<span id=\"cb4-11\"><a href=\"#cb4-11\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"co\"># Check @cmd if necessary<\/span><\/span>\n<span id=\"cb4-12\"><a href=\"#cb4-12\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>        <span class=\"kw\">return<\/span> <span class=\"ot\">&quot;<\/span><span class=\"st\">mocked cli output<\/span><span class=\"ot\">&quot;<\/span>;<\/span>\n<span id=\"cb4-13\"><a href=\"#cb4-13\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    });<\/span>\n<span id=\"cb4-14\"><a href=\"#cb4-14\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb4-15\"><a href=\"#cb4-15\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"kw\">my<\/span> <span class=\"dt\">$controller<\/span> = <span class=\"fu\">App::Workflow<\/span>::<span class=\"fu\">Controller<\/span>-&gt;new();<\/span>\n<span id=\"cb4-16\"><a href=\"#cb4-16\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    is(<span class=\"dt\">$controller<\/span>-&gt;<span class=\"dt\">some_other_method<\/span>(), <span class=\"ot\">&quot;<\/span><span class=\"st\">expected result<\/span><span class=\"ot\">&quot;<\/span>, <span class=\"ot\">&quot;<\/span><span class=\"st\">Test with mocked CLI<\/span><span class=\"ot\">&quot;<\/span>);<\/span>\n<span id=\"cb4-17\"><a href=\"#cb4-17\" aria-hidden=\"true\" tabindex=\"-1\"><\/a><\/span>\n<span id=\"cb4-18\"><a href=\"#cb4-18\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>    <span class=\"dt\">$controller_mock<\/span>-&gt;<span class=\"dt\">unmock<\/span>(<span class=\"ot\">&#39;<\/span><span class=\"ss\">_execute_cli<\/span><span class=\"ot\">&#39;<\/span>);<\/span>\n<span id=\"cb4-19\"><a href=\"#cb4-19\" aria-hidden=\"true\" tabindex=\"-1\"><\/a>};<\/span><\/code><\/pre>\n<\/div>\n<p>This approach tests the logic <em>around<\/em> the CLI call without<br \/>\ninterfering with <code>IPC::Run<\/code> itself.<\/p>\n<h2 id=\"lessons-learned\">Lessons Learned:<\/h2>\n<ol type=\"1\">\n<li><strong>Mocking Exported Functions:<\/strong> Always mock in the<br \/>\nnamespace of the <em>importing<\/em> module.<\/li>\n<li><strong>Compile-Time Dependencies:<\/strong> Use <code>BEGIN<\/code><br \/>\nblocks to mock dependencies needed when the module under test is<br \/>\nloaded.<\/li>\n<li><strong><code>IPC::Run<\/code> is Special:<\/strong> Directly mocking<br \/>\nthe imported <code>run<\/code> function from <code>IPC::Run<\/code> can be<br \/>\nfragile and lead to unexpected side effects like <code>$self<\/code><br \/>\ncorruption.<\/li>\n<li><strong>Wrap and Mock:<\/strong> Encapsulating external calls like<br \/>\n<code>IPC::Run::run<\/code> within internal methods provides a stable<br \/>\nseam for mocking, avoiding direct interference with the complex external<br \/>\nlibrary.<\/li>\n<li><strong>State Management:<\/strong> <code>our<\/code> variables with<br \/>\n<code>local<\/code> hash key assignments in subtests remain a good<br \/>\npattern for managing mock state across test cases.<\/li>\n<\/ol>\n<p>This debugging journey was a deep dive into the nuances of Perl\u2019s<br \/>\nsymbol table, compile-time vs.\u00a0runtime, and the limits of mocking. The<br \/>\nwrapper method pattern proved to be the most effective strategy for<br \/>\nhandling complex dependencies like <code>IPC::Run<\/code>.<\/p>\n\n<div class=\"twitter-share\"><a href=\"https:\/\/twitter.com\/intent\/tweet?via=cjamescollier\" class=\"twitter-share-button\">Tweet<\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>As a software engineer, I often find myself deep in the trenches of testing, ensuring the code I write is not just functional but also resilient. Recently, I embarked on a refactoring project that involved moving a script\u2019s functionality into a new Perl module, App::Workflow::Controller. This module\u2019s job was to orchestrate a few tasks: query [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[328,318,172,188,165,317,47,140,18,193,8],"tags":[],"class_list":["post-2086","post","type-post","status-publish","format-standard","hentry","category-328","category-bigquery","category-cj-insider","category-cli","category-databases","category-google-cloud-support","category-linux","category-november","category-perl","category-test","category-work"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1YDIB-xE","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/posts\/2086","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2086"}],"version-history":[{"count":8,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/posts\/2086\/revisions"}],"predecessor-version":[{"id":2094,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=\/wp\/v2\/posts\/2086\/revisions\/2094"}],"wp:attachment":[{"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2086"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2086"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wp.c9h.org\/cj\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2086"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}