<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.finnpedersenfrance.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.finnpedersenfrance.com/" rel="alternate" type="text/html" /><updated>2026-07-29T11:26:45+02:00</updated><id>https://www.finnpedersenfrance.com/feed.xml</id><title type="html">Finn Pedersen’s Blog</title><subtitle>A blog about technology and development for Microsoft Dynamics 365 Business Central.</subtitle><entry><title type="html">France says no to Tesla FSD (Politics)</title><link href="https://www.finnpedersenfrance.com/politics/2026/07/24/FSD-not-approved-in-france.html" rel="alternate" type="text/html" title="France says no to Tesla FSD (Politics)" /><published>2026-07-24T07:00:00+02:00</published><updated>2026-07-28T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/politics/2026/07/24/FSD-not-approved-in-france</id><content type="html" xml:base="https://www.finnpedersenfrance.com/politics/2026/07/24/FSD-not-approved-in-france.html"><![CDATA[<h2 id="france-says-no-to-tesla-fsd-for-now">France says no to Tesla FSD (for now)</h2>

<p><img src="/assets/images-01/PhilippeTabarot.jpg" alt="Philippe Tabarot" /></p>

<p>On July 22, 2026, the French Minister of Transport, <a href="https://www.info.gouv.fr/personnalite/philippe-tabarot"><strong>Philippe Tabarot</strong></a>, answered a citizen’s question on the French government’s participation platform <a href="https://www.agora.gouv.fr/qags/918b7863-c9e7-4dfb-9a2e-b54feb7e1629">Agora</a>. The answer, in short: <strong>No. Tesla FSD (Supervised) will not be authorized in France. Not yet.</strong></p>

<p>You can watch the video with the minister’s answer on the <a href="https://www.agora.gouv.fr/qags/918b7863-c9e7-4dfb-9a2e-b54feb7e1629">Agora platform</a>, and the decision was covered widely in the French press, for example by <a href="https://www.lefigaro.fr/secteur/high-tech/la-france-refuse-pour-le-moment-d-autoriser-le-systeme-de-conduite-autonome-supervisee-de-tesla-20260723">Le Figaro</a> and <a href="https://www.numerama.com/vroom/2299755-coup-dur-pour-tesla-le-fsd-ne-sera-pas-autorise-en-france.html">Numerama</a>. The Danish motoring organization FDM writes <a href="https://fdm.dk/nyheder/nyt-om-trafik-og-biler/tesla-ejere-i-danmark-faar-lov-at-slippe-rattet">Tesla-ejere i Danmark får lov at slippe rattet</a>, and the official website of Færdselsstyrelsen announces <a href="https://www.fstyr.dk/nyheder/2026/jun/teslas-foererassistentsystem-fsd-supervised-er-foreloebigt-godkendt-i-danmark">Teslas førerassistentsystem FSD Supervised er foreløbigt godkendt i Danmark</a>.</p>

<p>I live in Denmark, and here FSD (Supervised) was <a href="https://electrek.co/2026/06/09/tesla-fsd-approved-denmark-fourth-european-country/">provisionally approved on June 9, 2026</a> by the Danish Road Traffic Authority, Færdselsstyrelsen. The Netherlands was <a href="https://www.rdw.nl/en/news/2026/rdw-explanation-of-european-type-approval-tesla-with-provisional-validity-in-the-netherlands">first, on April 10, 2026</a>, when the Dutch vehicle authority RDW granted a European type approval with provisional validity limited to the Netherlands, after more than one and a half years of testing. Lithuania, Estonia, and Belgium followed.</p>

<p>The minister’s answer reveals a lot, and that is what this article investigates.</p>

<h2 id="the-question-from-ludovic">The question from Ludovic</h2>

<blockquote>
  <p>Supervised autonomous driving, approved in the Netherlands, could help save lives by limiting human error. Is France considering authorizing it soon?</p>
</blockquote>

<p>The full transcript of the minister’s answer, translated into English, is at the end of this article. Read it. It is worth your time.</p>

<h2 id="intro-i-bought-fsd-in-2019">Intro: I bought FSD in 2019</h2>

<p>In 2019, I bought a Tesla Model 3 and added the FSD option. <strong>I have been waiting for seven years!</strong></p>

<h3 id="what-does-it-mean-to-me">What does it mean to me?</h3>

<h4 id="driving-from-denmark-to-france">Driving from Denmark to France</h4>

<p>I never thought of letting the car drive in the city. My interest was letting it drive the 1.200 km on the highway from Denmark to France. That would have been really useful.</p>

<p>If FSD cannot cross the borders into Sweden, Germany, the Netherlands, Belgium, and France, then it is just a gadget.</p>

<h4 id="people-who-are-blind-or-have-low-vision">People who are blind or have low vision</h4>

<p>My son was slowly losing his vision at that time, and I told him that by the time he turned 18 in 2026, they would have solved the challenge and that one day, he would be able to own a car.
Little did I expect that public administration would get in the way of the technology.</p>

<h4 id="safety">Safety</h4>

<p>In 1987, my mother was killed in her car when a truck driver lost control of his vehicle.
To me, it is personal, and I have no problem with all trucks becoming autonomous.</p>

<h4 id="electric-car">Electric Car</h4>

<p>I have driven almost 200.000 km in my Tesla Model 3. Assuming a comparable gasoline car consuming 5 liters per 100 km, that corresponds to roughly 10.000 liters of gasoline that would have had to be pumped out of the ground, refined, and transported to a gas station. Burning that gasoline would have produced about 23 metric tons of CO2 in tailpipe emissions alone. The comparison covers tailpipe emissions only and does not include the emissions from producing the electricity I charged with.</p>

<h2 id="everyone-else-is-late-to-the-party">Everyone else is late to the party</h2>

<h3 id="tesla">Tesla</h3>

<p>Tesla has been talking about self-driving cars since 2016.
All of their models, including the cheapest Model 3, have been equipped with cameras and an onboard computer intended for self-driving since 2017, though the hardware has been upgraded several times since then.</p>

<p>On the
<a href="https://www.youtube.com/watch?v=j0z4FweCy4M">Tesla AI Day 2021</a>,
they shared progress, data, problems, and information.</p>

<p>Tesla does not publish a cumulative figure for its FSD investment, but my estimate is that the company has spent more than $14 billion on AI infrastructure, research, and development over the past 10 years. Not all of that is FSD-specific, but the order of magnitude is clear.</p>

<h3 id="the-european-union">The European Union</h3>

<p>Now that Tesla is actually rolling out FSD Supervised on public roads, Europe is reacting, or should I say <em>panicking</em>.</p>

<p>The good news is that the EU understands that it has to harmonize the rules for all countries.
No European car maker will ever succeed in making autonomous vehicles for all the different countries in the world. Especially the small ones. This should also be good news for human drivers and other car makers.</p>

<h4 id="2025-03-05-industrial-action-plan-for-the-european-automotive-sector">2025-03-05 Industrial Action Plan for the European automotive sector</h4>

<p>Get the
<a href="https://transport.ec.europa.eu/document/download/89b3143e-09b6-4ae6-a826-932b90ed0816_en?filename=Communication%20-%20Action%20Plan.pdf">PDF</a>
here.</p>

<h4 id="2026-06-08-directorate-general-for-mobility-and-transport">2026-06-08 Directorate-General for Mobility and Transport</h4>

<p>18 Member States signed a Joint Declaration of Intent on large-scale cross-border testbeds for autonomous vehicles.</p>

<p><a href="https://transport.ec.europa.eu/news-events/news/eu-ministers-back-cross-border-initiative-autonomous-vehicle-testbeds-2026-06-08_en">EU Ministers back cross-border initiative for autonomous vehicle testbeds</a></p>

<p>Note that as a first supporting measure, the Connecting Europe Facility (CEF) 2026 work programme foresees 20 million euros for digital infrastructure dedicated to autonomous driving under the reflow call to be launched in June this year.</p>

<p>Twenty million euros will not get them far. Tesla, a single company, has had to spend billions of dollars, and even Tesla is not there yet.</p>

<h2 id="why-are-we-interested-in-autonomous-vehicles">Why are we interested in autonomous vehicles?</h2>

<h3 id="autonomous-vehicles-can-save-lives">Autonomous vehicles can save lives</h3>

<p>That is the promise, according to <a href="https://x.com/elonmusk/status/2079965837382037520">Elon Musk on X</a>, and it is the reason Ludovic asked his question in the first place.</p>

<h3 id="some-people-cannot-or-prefer-not-to-drive-a-car">Some people cannot or prefer not to drive a car</h3>

<ul>
  <li>People with low vision or other disabilities</li>
  <li>People who have a driver’s license but are not comfortable driving</li>
  <li>People with no driver’s license</li>
</ul>

<h2 id="protection-of-the-european-car-industry">Protection of the European car industry</h2>

<p>This looks like protectionism to me. France, Germany, and the other car-making countries in Europe have strong industrial reasons to delay Tesla, and I suspect that protecting the European car industry is part of the explanation.</p>

<p>The minister presents this as a pure safety decision. But notice that the countries that approved FSD first, the Netherlands, Lithuania, Estonia, Denmark, and Belgium, have one thing in common: <strong>none of them has a major national car brand to protect</strong>.</p>

<h2 id="you-need-data-to-train-ai">You need data to train AI</h2>

<p>The car industry and the minister are overlooking something fundamental about AI: <strong>you need data to train it on</strong>.</p>

<p>In October 2016, almost ten years ago, Tesla announced that every car leaving the factory would be equipped with eight cameras and the hardware for full self-driving. Tesla has been collecting real-world driving data ever since. Its camera-equipped fleet has driven billions of kilometers, and Tesla can select and upload the real-world driving clips that are useful for training. It does not need to transmit every kilometer to learn from the fleet.</p>

<p>European car makers also collect driving data. But none of them combines Tesla’s fleet scale, standardized hardware, and nearly a decade of continuous deployment. Without comparable data, I do not see how they will catch up.</p>

<p>In the video, the minister reminds us that France has some of the best engineers in the world. That is true.</p>

<p>But this is not a traditional engineering problem. You cannot specify, design, and engineer your way to autonomous driving.</p>

<blockquote>
  <p>Without data, there is no AI. And you cannot buy ten years of driving data. You have to collect it.</p>
</blockquote>

<h2 id="this-only-concerns-a-few-cars-anyway">This only concerns a few cars anyway</h2>

<p>The current European rollout only concerns the newer Tesla models with <strong>Hardware 4</strong>. And the customer has to pay a subscription of <strong>99 euros per month</strong> to activate FSD.</p>

<p>That will further limit the number of users actually activating FSD in France. We are not talking about millions of cars suddenly driving themselves. We are talking about a small, careful, and paying group of early adopters.</p>

<h2 id="the-driver-is-responsible">The driver is responsible</h2>

<p>This is important, and the minister actually says it himself: <strong>FSD (Supervised) is a driver assistance system, not an autonomous driving system</strong>.</p>

<p>The driver has to be in the driver’s seat, pay attention, and be ready to intervene at any moment. The driver remains fully responsible, including legally, even when FSD is activated.</p>

<p>So the safety net the minister is asking for is already there. It is the driver.</p>

<h2 id="once-approved-the-customers-will-move">Once approved, the customers will move</h2>

<p>In the <a href="https://stockanalysis.com/stocks/tsla/transcripts/653184-q2-2026/">Q2 2026 earnings call</a>, held the very same day as the minister’s video, Elon Musk said:</p>

<blockquote>
  <p>We’re seeing in locations that have FSD approved, we’re seeing a very high take rate of FSD. In fact, I think for a lot of people, they’re actually buying Tesla Full Self-Driving with a car attached, as opposed to a car with FSD.</p>
</blockquote>

<p>Customers are walking into the stores asking for Full Self-Driving, with whatever car it comes with. About 55% of North American deliveries in Q2 had an FSD subscription enabled at the time of delivery.</p>

<p>This supports the point. <strong>Once FSD is approved in Europe, the rest of the car industry will lose even more customers to Tesla.</strong> The French car industry knows it. The German car industry knows it. And I suspect the minister knows it too.</p>

<p>Delaying the approval does not change this. It only delays the moment the European car industry has to face it.</p>

<h2 id="the-ministers-answer-full-transcript-in-english">The minister’s answer (full transcript in English)</h2>

<p>Here is the full transcript of Philippe Tabarot’s video answer, translated into US English with my comments.</p>

<blockquote>
  <p>Hello everyone, I am very happy to answer today a question that has attracted a lot of interest: Ludovic’s question about Tesla’s FSD driver assistance system.</p>

  <p>So that everyone understands what this is about: this system is a driver assistance system developed by Tesla for its vehicles. It is a very advanced driver assistance system whose goal is to drive in all environments, executing all maneuvers automatically and driving hands-free. Until now, these driver assistance systems were mostly oriented toward highways, and in general, at least one hand must remain on the steering wheel. So this is a significant change.</p>
</blockquote>

<p>At least Tesla is getting the credit that their system is very advanced. Well, they spent billions of dollars over 10 years on a bold prediction.</p>

<blockquote>
  <p>The Netherlands has decided to depart from the international and European regulatory framework and authorize its use on their territory.</p>
</blockquote>

<p>Unbelievable. Mr. Tabarot is criticizing the Netherlands for moving forward. For reference, see the
<a href="https://www.rdw.nl/en/news/2026/rdw-explanation-of-european-type-approval-tesla-with-provisional-validity-in-the-netherlands">RDW explanation of European type approval Tesla with provisional validity in the Netherlands</a>.</p>

<blockquote>
  <p>In France, we consider that this system brings a certain number of technological advances, but that the trade-offs in terms of safety are not yet sufficient for an authorization as it stands. Especially since this system is not an autonomous driving system: the driver remains responsible for driving the vehicle at all times, including when FSD is activated.</p>
</blockquote>

<p>This is strange and in contradiction to the RDW: “<em>Safety is RDW’s top priority.</em>” and “<em>We have thoroughly examined and verified this system for more than one and a half years.</em>”</p>

<blockquote>
  <p>Moreover, several other European countries share these reservations about the system’s level of safety. Our position is therefore not isolated.</p>
</blockquote>

<p>Why is it necessary to emphasize that France is not alone? This is completely irrelevant.</p>

<blockquote>

  <p>So what are these concerns? Without going into overly technical details, there are several. First example: we have found that the system allows speeding. If it notices that the vehicles around it are driving faster, in plain terms, it can drive at 70 km/h instead of 50 if traffic is moving at that speed, and this without any explicit request from the driver. In some cases, a vehicle can thus go up to 50% above the authorized speed limit. The rules of the road are therefore not respected.</p>
</blockquote>

<p>I find this argument absurd, and much of the reaction on social media has been the same. The system acts human-like, drives like anyone else would, yet you argue that FSD should “police” the other drivers. Nonsense. It makes no sense to make such requirements.</p>

<blockquote>
  <p>Another example: according to the data we have, the system does not guarantee an optimal level of driver attention in the city and during the most complex maneuvers, such as lane changes, crossing intersections, or roundabouts. Yet a lack of attention can have very serious consequences, both for people outside the vehicle and for its occupants. And let me remind you: even when the driver assistance system is activated, the driver remains criminally liable in the event of an accident.</p>
</blockquote>

<p>We are talking about FSD <strong>Supervised</strong>. The whole point is that the driver is responsible and present.</p>

<p>Philippe Tabarot says no to Tesla FSD on safety grounds, while he has earlier acknowledged that drug use is a growing problem among drivers in France.</p>

<p>To understand this, I invite you to read a post from Philippe Tabarot on LinkedIn one year ago.</p>

<p><a href="https://www.linkedin.com/feed/update/urn:li:activity:7323410878865764354/">Il y a 3 mois jour pour jour, Joana, 15 ans, perdait la vie à Châteaudun dans un accident de car scolaire.</a></p>

<blockquote>
  <p>Exactly three months ago today, 15-year-old Joana lost her life in Châteaudun in a school bus accident.</p>

  <p>The driver’s positive cannabis test revealed an unacceptable reality: drug use, a scourge in our society, can also affect those responsible for transporting our country’s primary school pupils and middle and high school students.</p>

  <p>This is unacceptable for families who place their children, in complete trust, in the care of school transport professionals.</p>
</blockquote>

<p>Would you rather put your kid in a French school bus or in an autonomous Tesla?</p>

<p><a href="https://www.tf1info.fr/societe/transports-scolaires-deja-30-000-controles-realises-depuis-le-debut-de-l-annee-annonce-le-ministere-2392641.html">By the end of August 2025, nearly 30.000 school bus drivers had been checked.</a></p>

<ul>
  <li>119 tested positive for drugs;</li>
  <li>63 tested positive for alcohol;</li>
  <li>337 other offenses were reported.</li>
</ul>

<p><strong>These people are driving your kids to school!</strong> These are not average drivers.</p>

<blockquote>
  <p>So what are the next steps? We are currently continuing our technical discussions with the Netherlands, the other European states, and Tesla to refine our analysis. Last week, a representative of the Ministry of Transport actually traveled to the Netherlands.</p>
</blockquote>

<p>Wow. One person went to the Netherlands. I am <strong>not</strong> impressed.</p>

<blockquote>
  <p>I want to emphasize this: France supports these technologies, and their deployment must take place within a framework of trust that makes it possible to evaluate the systems and determine the adjustments to FSD needed to guarantee a high level of safety.</p>
</blockquote>

<p>I am sure they do.</p>

<blockquote>
  <p>Ludovic, I hope these explanations help you better understand the ongoing debates. At a time when the number of road accidents is rising again, my responsibility is to enable innovation without ever compromising on safety. Driver assistance systems as advanced as Tesla’s are new because they are designed to operate in all driving environments, including in the city, where situations are particularly complex. It is therefore normal that we take the time to evaluate them properly, to protect the driver as well as all road users.</p>
</blockquote>

<p>I think there are some important observations here:</p>

<ol>
  <li>How many Teslas with FSD activated would actually be driving around in France? Tesla is a fairly expensive car in France, and paying 99 € per month for the feature will definitely hold some back. Also remember that only the newer cars with Hardware 4 can activate the option.</li>
  <li>We can conclude that only fairly rich people with newer cars will actually buy it, and eventually use it some of the time.</li>
  <li>So how high is the actual risk that Tesla FSD causes an accident compared with all the accidents happening in France?</li>
</ol>

<p>See last section.</p>

<blockquote>
  <p>France has always supported the deployment of new driver assistance technologies. We have already granted exemptions in the past for this type of system when it met the safety requirements. This is the case, for example, with Ford and, recently, BMW. And we must look even further ahead: the goal is to move from driver assistance to autonomous driving. In France, more than 200 autonomous vehicle experiments have been carried out since 2015. That is a solid foundation, but I want to go further and enable the arrival of type-approved autonomous vehicles in France as early as 2027.</p>
</blockquote>

<p>So we have to wait until next year.</p>

<blockquote>
  <p>It is in this spirit that France, together with Germany, Luxembourg, and 15 other countries, has launched the European test site initiative for autonomous vehicles. The goal is to harmonize the rules between countries and enable their large-scale deployment across Europe in complete safety.</p>
</blockquote>

<p>This is great news. Actually, the only thing to be excited about.</p>

<blockquote>
  <p>In the fall, I will bring together the French autonomous vehicle ecosystem to present the levers that will accelerate the deployment of this technology and set a course for the coming years.</p>
</blockquote>

<p>Now this is interesting. He is delaying Tesla FSD until next year and gathering the French auto industry in the fall. I will be looking forward to seeing what comes out of this.</p>

<blockquote>
  <p>In France, we are fortunate to have world-class car manufacturers and automotive suppliers, but also some of the best engineers in the world in the field of artificial intelligence and digital technology. We have a real card to play, and we will be there, but never at the expense of safety on our roads.</p>
</blockquote>

<p>And in the end, I believe this is what it is all about: the French automotive industry is behind.</p>

<p>Yes. France has world-class engineering universities and Grandes Écoles. France has engineers and mathematicians. France has <a href="https://mistral.ai/">Mistral.ai</a>. France might even find some money, and it has electricity.</p>

<h2 id="what-fsd-is-up-against-the-french-driver">What FSD is up against: the French driver</h2>

<p>The French government publishes an annual report on traffic violations. The
<a href="https://www.onisr.securite-routiere.gouv.fr/etat-de-linsecurite-routiere/bilans-annuels-infractions-et-permis-points/bilan-2024-des-infractions-au-code-de-la-route">Bilan 2024 des infractions au code de la route</a>
from the national road safety observatory (ONISR) gives the reader a true picture of what FSD is up against: the human French driver.</p>

<p>In 2024 alone:</p>

<ul>
  <li>More than <strong>27,6 million traffic violations</strong> were recorded.</li>
  <li>Approximately <strong>1,1 million of them were criminal road offenses</strong> (<em>délits</em>), an increase of 9,5% from 2023. Of these, 25% concerned driving without insurance, 18% hit-and-run (<em>délit de fuite</em>), and 14% driving without a license or with an invalid license.</li>
  <li><strong>13,9 million automated speeding notices</strong> were issued.</li>
  <li>More than <strong>270.000 automated notices</strong> were issued for running red lights or railway-crossing signals.</li>
  <li><strong>11,7 million license points were withdrawn</strong>: 5,7 million for speeding, 1,9 million for failure to respect priority rules, and 1,7 million for using a phone or earphones behind the wheel.</li>
</ul>

<p>And here is the number that should keep the minister awake at night: only 79% of all French drivers still have all 12 points on their license. Among the presumed perpetrators of fatal accidents, only 49% did.</p>

<p>It gets worse. In 2023, traffic offenses accounted for <strong>42% of all criminal convictions in France</strong>, and 58% of those involved driving under the influence of alcohol and/or drugs.</p>

<p>And who are these convicted drivers? The report answers that too. In 2023, 205.536 men and 21.473 women were convicted of traffic offenses. <strong>91% of all convictions concerned men</strong>, and 52% concerned men between 18 and 34 years old. Men account for 92,4% of the convictions for driving under the influence of drugs, 91,9% for driving despite a suspended license, and 88,2% for drunk driving. The age profile is just as clear: driving without a license peaks among the 18-24 year olds, driving without insurance and driving despite a suspension peak among the 25-34 year olds, and drunk driving is concentrated among the 25-44 year olds.</p>

<p>FSD has no gender and no age. It does not turn 19 and start showing off.</p>

<p>Now go back and read the minister’s main argument again: FSD might drive 70 km/h in a 50 zone if the surrounding traffic does. The surrounding traffic. The one collecting 13,9 million speeding notices a year.</p>

<blockquote>
  <p>FSD does not drink. It does not take drugs. It does not text behind the wheel. It does not lose its license. And it does not deliberately flee the scene of an accident. FSD can make errors, but not for the same reasons as human drivers.</p>
</blockquote>

<p>This is my answer to the third observation above. The question is not whether FSD is perfect. The question is whether it is better than this.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I want to give the minister, Philippe Tabarot, credit for one thing: he answered a citizen’s question, on camera, with arguments. That is how a democracy should work.</p>

<p>I am also happy that the EU is harmonizing traffic rules. It will benefit everyone.</p>

<p>But EU bureaucracy does not advance technology. It has never invented anything. It makes rules.</p>

<p>As the joke goes:</p>

<ul>
  <li>America innovates</li>
  <li>China copies</li>
  <li><a href="https://artificialintelligenceact.eu/">Europe regulates</a></li>
</ul>

<p>The old continent is living up to its reputation once again.</p>]]></content><author><name>Finn</name></author><category term="Politics" /><summary type="html"><![CDATA[France says no to Tesla FSD (for now)]]></summary></entry><entry><title type="html">There is a new sheriff in town (Business Central)</title><link href="https://www.finnpedersenfrance.com/programming/2026/07/22/there-is-a-new-sheriff-in-town.html" rel="alternate" type="text/html" title="There is a new sheriff in town (Business Central)" /><published>2026-07-22T07:00:00+02:00</published><updated>2026-07-24T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2026/07/22/there-is-a-new-sheriff-in-town</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2026/07/22/there-is-a-new-sheriff-in-town.html"><![CDATA[<h2 id="there-is-a-new-sheriff-in-town">There is a new sheriff in town</h2>

<p><img src="/assets/images-01/NewSheriffInTownV2.png" alt="Robot Sheriff and Dinosaur in jail" /></p>

<p><a href="https://github.com/StefanMaron/BusinessCentral.LinterCop">BusinessCentral.LinterCop</a> is deprecated and <a href="https://alcops.dev/">ALCops</a> is the new sheriff in town.</p>

<p>Well, actually, there are six:</p>

<table>
  <thead>
    <tr>
      <th>Analyzer</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/applicationcop/">ApplicationCop</a></td>
      <td>Enforces Business Central application conventions for tables, pages, enums, labels, and permissions.</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/documentationcop/">DocumentationCop</a></td>
      <td>Validates that AL code is properly documented with comments and XML documentation.</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/formattingcop/">FormattingCop</a></td>
      <td>Enforces consistent code formatting and visual structure.</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/lintercop/">LinterCop</a></td>
      <td>Flags code quality issues, measures complexity, and promotes modern AL patterns.</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/platformcop/">PlatformCop</a></td>
      <td>Detects code that is technically broken, dangerous, or silently ignored at the AL platform level.</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/testautomationcop/">TestAutomationCop</a></td>
      <td>Validates the structure and correctness of AL test code.</td>
    </tr>
  </tbody>
</table>

<h2 id="motivation">Motivation</h2>

<p>If you need to know more about the motivation for this change, you can find the explanation in Stefan Maron’s article
<a href="https://stefanmaron.com/posts/introducing-alcops/">Introducing ALCops</a>
and watch this video on YouTube:
<a href="https://www.youtube.com/watch?v=tMqCSibSRug">ALCops: LinterCop’s Successor for Business Central AL Code Analysis</a></p>

<h2 id="migration-from-businesscentrallintercop-to-alcops">Migration from BusinessCentral.LinterCop to ALCops</h2>

<p>I have made an enhanced version of the <strong>LinterCop Migration Script</strong>, which you can find on GitHub here: <a href="https://github.com/finnpedersenkazes/LinterCop-Migration-Script">https://github.com/finnpedersenkazes/LinterCop-Migration-Script</a>.</p>

<p>Original: <a href="https://alcops.dev/docs/lintercop-migration-script/">https://alcops.dev/docs/lintercop-migration-script/</a></p>

<h2 id="installation">Installation</h2>

<p>Find the VS Code extension on the <a href="https://marketplace.visualstudio.com/items?itemName=Arthurvdv.alcops">Visual Studio Code Marketplace</a>.</p>

<p>Or install it directly from Visual Studio Code.</p>

<p>Source: <a href="https://alcops.dev/docs/getting-started/vscode/">Getting Started</a></p>

<blockquote>
  <p>Don’t forget to uninstall the <strong>BusinessCentral.LinterCop</strong> extension.</p>
</blockquote>

<h2 id="setup">Setup</h2>

<p>Look for <strong>ALCops</strong> in the menu bar at the bottom of VS Code or open the Command Palette (<code class="language-plaintext highlighter-rouge">Ctrl+Shift+P</code>) and search for ALCops. Then select <em>ALCops: Select Code Analyzers</em>.</p>

<h3 id="settingsjson">settings.json</h3>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"al.enableCodeAnalysis"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"al.codeAnalyzers"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"${CodeCop}"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${UICop}"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${PerTenantExtensionCop}"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.ApplicationCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.DocumentationCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.FormattingCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.LinterCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.PlatformCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.TestAutomationCop.dll"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"${analyzerFolder}ALCops.Common.dll"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"alcops.automaticUpdates"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"alcops.updateNotification"</span><span class="p">:</span><span class="w"> </span><span class="s2">"notify-only"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"alcops.versionChannel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stable"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"alcops.checkUpdateInterval"</span><span class="p">:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
    </span><span class="nl">"al.backgroundCodeAnalysis"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Project"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"al.browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Edge"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"al.ruleSetPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../.BlueCircle360Certified/Level-4/Main.ruleset.json"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p>Don’t forget to remove the old <strong>BusinessCentral.LinterCop</strong> settings.</p>
</blockquote>

<h2 id="what-is-new">What is new?</h2>

<p>My code is normally warning-free.</p>

<p>So what did <strong>ALCops</strong> detect in my recent project?</p>

<table>
  <thead>
    <tr>
      <th>Rule ID</th>
      <th>Description</th>
      <th>Category</th>
      <th>Code Fix</th>
      <th>Severity</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/platformcop/pc0037/">PC0037</a></td>
      <td>Use Validate() instead of direct field assignment</td>
      <td>Design</td>
      <td>Yes</td>
      <td>Warning</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/platformcop/pc0030/">PC0030</a></td>
      <td>Use partial records on read operation</td>
      <td>Performance</td>
      <td>Yes</td>
      <td>Info</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/platformcop/pc0029/">PC0029</a></td>
      <td>Use CreateSequentialGuid for key fields</td>
      <td>Performance</td>
      <td>Yes</td>
      <td>Info</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/documentationcop/dc0007/">DC0007</a></td>
      <td>Public objects must include XML documentation comments</td>
      <td>Design</td>
      <td>No</td>
      <td>Info</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/documentationcop/dc0009/">DC0009</a></td>
      <td>Events must include XML documentation comments</td>
      <td>Design</td>
      <td>No</td>
      <td>Hidden</td>
    </tr>
    <tr>
      <td><a href="https://alcops.dev/docs/analyzers/applicationcop/ac0032/">AC0032</a></td>
      <td>Unused permission declared</td>
      <td>Design</td>
      <td>Yes</td>
      <td>Info</td>
    </tr>
  </tbody>
</table>

<p>I have now started reviewing these cases to better understand what should be done.</p>

<h2 id="conclusion">Conclusion</h2>

<p><strong>AL code analysis</strong> helps you keep your code cleaner, safer, and faster.</p>

<p>It helps you find problems early, which will save you time and money.</p>

<p>But more importantly, it creates a coding standard for your team, for now and in the future.</p>

<blockquote>
  <p>Clean Code is about respect for the developer coming after you.</p>
</blockquote>

<p>Reach out to me if you need help getting started with AL code analysis.</p>

<h2 id="observations">Observations</h2>

<h3 id="fc0004-permission-declarations-should-be-ordered-alphabetically">FC0004 Permission declarations should be ordered alphabetically</h3>

<p>I ended up disabling FC0004 in my permissionset.al files because it conflicted with auto save.</p>

<h3 id="lc0095-parameter-is-not-referenced">LC0095 Parameter is not referenced</h3>

<p>LC0095 had to be disabled in EventSubscriber Codeunits because most functions don’t use all parameters.</p>

<h3 id="pc0037-use-validate-instead-of-direct-field-assignment">PC0037 Use Validate() instead of direct field assignment</h3>

<p>PC0037 forced me to move all TableRelation properties from Table Fields to Page Fields, which probably is a good thing.</p>

<p>The <strong>TableRelation</strong> property provides two things. Control that the value is already in the related table and a lookup link. I needed the lookup link, so I moved the property to the field on the page. I am <strong>not</strong> testing the page.</p>

<p>I am writing <a href="/programming/2025/06/26/temporary-tables-in-tests.html">super fast tests using Temporary tables</a>.
Using Validate() everywhere collided with checking Table Relations on the table.
Since I aime for 100% code coverage of the objects I test,
I moved the Table Relation property to the page.</p>

<p>I prioritized to keep my tests simple and fast, at the expense of having to keep track of the
Table Relation on both the Card Page and the List Page. I hope I made the right decision.</p>

<h2 id="recommendations">Recommendations</h2>

<h3 id="how-to-disable-a-rule-in-a-special-case">How to disable a rule in a special case</h3>

<p>Sometimes you have to accept an antipattern or wait for the base application to catch up.</p>

<blockquote>
  <p>LC0088: Prefer Enum over Option type</p>
</blockquote>

<p>In this case, the base function only accepts an option. For readability, I have given it one.</p>

<pre><code class="language-al">#pragma warning disable LC0088
    [NonDebuggable]
    internal procedure CreateHash(TextJson: Text; SecretKey: SecretText) Hash: SecretText
    var
        CryptographyManagement: Codeunit System.Security.Encryption."Cryptography Management";
        HashAlgorithmType: Option HMACMD5,HMACSHA1,HMACSHA256,HMACSHA384,HMACSHA512;
    begin
        Hash := CryptographyManagement.GenerateHash(TextJson, SecretKey, HashAlgorithmType::HMACSHA256);
    end;
#pragma warning restore LC0088
</code></pre>

<h3 id="event-subscriber-codeunit">Event Subscriber Codeunit</h3>

<p>Always keep your Event Subscribers in a dedicated Codeunit.</p>

<blockquote>
  <p>LC0095: Parameter is not referenced</p>
</blockquote>

<p>Typically, you are not using all the parameters the Event Publisher is offering,
therefore you need to disable the rule.</p>

<p>Start and end your object file with a <code class="language-plaintext highlighter-rouge">#pragma warning</code> to <code class="language-plaintext highlighter-rouge">disable</code> the rule for the entire Codeunit.</p>

<pre><code class="language-al">#pragma warning disable LC0095

codeunit 50000 "Event Subscribers"
{
...
}

#pragma warning restore LC0095
</code></pre>

<h2 id="clean-al-code-initiative">Clean AL Code Initiative</h2>

<ul>
  <li>Part 1: <a href="/programming/2023/10/10/BC-Rulesets.html">Rulesets in Business Central</a></li>
  <li>Part 2: <a href="/programming/2023/12/28/Namespaces-in-AL.html">Namespaces in AL</a></li>
  <li>Part 3: <a href="/programming/2023/12/29/Extensions-for-AL.html">VS Code Extensions for AL</a></li>
  <li>Part 4: <a href="/programming/2024/01/03/Automated-Tests-in-AL.html">Automated Tests in AL</a></li>
  <li>Part 5: <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced CodeCop Analyzer and Custom Rulesets</a></li>
  <li>Part 6: <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a></li>
  <li>Part 7: <a href="/programming/2024/01/12/TryFunctions-in-AL.html">Preconditions and TryFunctions in AL</a></li>
  <li><strong>Part 8</strong>: <a href="/programming/2026/07/22/there-is-a-new-sheriff-in-town.html">There is a new sheriff in town</a></li>
</ul>

<h2 id="super-fast-tests-covering-100-of-your-code">Super fast tests covering 100% of your code</h2>

<ul>
  <li>Part 1: <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces - Part 1 - System Environment</a></li>
  <li>Part 2: <a href="/programming/2025/06/26/environment-interface-part-2.html">Environment Interfaces - Part 2 - External API</a></li>
  <li>Part 3: <a href="/programming/2025/06/26/environment-interface-part-3.html">Environment Interfaces - Part 3 - Standard Application</a></li>
  <li>Part 4: <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a></li>
</ul>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[There is a new sheriff in town]]></summary></entry><entry><title type="html">The urgent need to modernize Business Central development</title><link href="https://www.finnpedersenfrance.com/programming/2025/11/30/urgent-need-to-modernize-bc-development.html" rel="alternate" type="text/html" title="The urgent need to modernize Business Central development" /><published>2025-11-30T06:00:00+01:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/11/30/urgent-need-to-modernize-bc-development</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/11/30/urgent-need-to-modernize-bc-development.html"><![CDATA[<h2 id="the-urgent-need-to-modernize-business-central-development">The urgent need to modernize Business Central development</h2>

<p><img src="/assets/images-01/DinosaurAIandBCnew.png" alt="Dinosaur AI and Business Central" /></p>

<h2 id="we-are-at-an-inflection-point">We are at an inflection point</h2>

<p>I am just back from <a href="https://www.directionsforpartners.com/emea2025">Directions EMEA 2025</a> where I have spoken with a number of people who show the way forward.</p>

<p>Maybe the inflection point was there before, but AI has definitely made it more obvious.</p>

<blockquote>
  <p><strong>Artificial Intelligence</strong> is the <em>asteroid</em> hurtling down and hitting our ecosystem.</p>
</blockquote>

<h2 id="business-central-is-fundamentally-different-from-dynamics-nav">Business Central is fundamentally different from Dynamics NAV</h2>

<blockquote>
  <p>It is an error not to make enough sense of urgency.</p>
</blockquote>

<p>This is not just about upgrading from Dynamics NAV to BC. <strong>Business Central development is fundamentally different</strong> from Dynamics NAV development, and this difference is accelerating.</p>

<h3 id="the-upgrade-cycle-has-changed-forever">The upgrade cycle has changed forever</h3>

<p>In Dynamics NAV, you could upgrade every five or ten years. You had time. You could write code that worked, even if it didn’t follow best practices. The upgrade cycle was slow enough that technical debt could accumulate without immediate consequences.</p>

<p><strong>Business Central changes the rules completely:</strong></p>

<ul>
  <li><strong>Monthly updates:</strong> Your code is updated every month, whether you like it or not</li>
  <li><strong>Breaking changes:</strong> With two major releases per year, breaking changes arrive on a regular basis</li>
</ul>

<h3 id="the-problem-isnt-newits-just-more-urgent">The problem isn’t new—it’s just more urgent</h3>

<p>The truth is, Dynamics NAV had programming guidelines. Microsoft provided them. But many developers created their own patterns, and it worked because upgrades were rare.</p>

<p><strong>That approach is much harder to sustain now.</strong></p>

<p>When you’re facing monthly updates and breaking changes, code that doesn’t follow modern practices becomes a challenge. It can break, cost more to fix, and frustrate customers. And it may put you at a competitive disadvantage.</p>

<h2 id="the-customer-perspective">The customer perspective</h2>

<p>Customers are often unaware of the technical debt hidden in their codebase. They don’t know to ask for:</p>

<ul>
  <li>Code that follows Microsoft’s guidelines</li>
  <li>Test coverage</li>
  <li>Modern architecture patterns</li>
  <li>Regular code reviews</li>
</ul>

<p>They trust that “senior developers with a lot of experience” are delivering quality code. But experience without modern practices can sometimes lead to legacy code.</p>

<h2 id="the-ai-acceleration">The AI acceleration</h2>

<p>The world is moving fast. AI tools are making it possible to build better solutions faster. Competitors who adopt modern practices and leverage AI will outcompete those who don’t.</p>

<p>If you’re still writing code the Dynamics NAV way, you may find yourself falling further behind every month.</p>

<h2 id="what-modern-business-central-development-looks-like">What modern Business Central development looks like</h2>

<p>Modern BC development requires:</p>

<ul>
  <li><strong>Investment in training:</strong> Understanding BC’s architecture, not just porting Dynamics NAV code</li>
  <li><strong>Test-driven development:</strong> Writing tests that run in milliseconds (see my articles on <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces</a> and <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a>)</li>
  <li><strong>Code review:</strong> Peer review of all changes (see my article on <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a>)</li>
  <li><strong>Modern tools:</strong> CodeCop, linters, proper settings.json configuration (see my article on <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced Rulesets</a>)</li>
  <li><strong>Clean architecture:</strong> Namespaces, interfaces, proper organization (see my <a href="/programming/2023/10/10/BC-Rulesets.html">Clean Code Initiative</a>)</li>
  <li><strong>Engineering management:</strong> Teams that understand modern software development</li>
</ul>

<h2 id="the-cost-of-delay">The cost of delay</h2>

<blockquote>
  <p>We’ll upgrade to BC during the next two years.</p>
</blockquote>

<p>But in two years, everything will have moved so far from where we are now. This year alone has seen rapid changes. Waiting two years means you’ll be even further behind, with even more technical debt to address.</p>

<p><strong>The time to act is now.</strong></p>

<h2 id="how-to-identify-legacy-code-patterns">How to identify legacy code patterns</h2>

<p>After three years as a freelance BC developer, I’ve seen patterns that suggest code may not be ready for BC’s update cycle:</p>

<ul>
  <li>Objects organized by Object Type instead of functionality</li>
  <li>Global variables everywhere</li>
  <li>No test apps</li>
  <li>Minimal or missing settings.json configuration</li>
  <li>No CodeCop or other linters</li>
  <li>No use of interfaces or namespaces</li>
  <li>Inconsistent naming conventions</li>
  <li>No automatic code cleanup</li>
  <li>No peer code review process</li>
</ul>

<p>If this describes your codebase, it is urgent time to get started fixing it.</p>

<h2 id="the-path-forward">The path forward</h2>

<p>I have changed my job title to <a href="https://www.linkedin.com/in/finnpedersen/">Modern Business Central Developer</a>.</p>

<blockquote>
  <p>Prefer to rewrite code using modern patterns rather than patching old code.</p>
</blockquote>

<p>If you have adopted a modern development mindset, then show it by changing your job title to <strong>Modern Business Central Developer</strong>.</p>

<p>And, please, by all means share this article with your network and ask your network to join the movement.</p>

<h2 id="we-need-to-outcompete-legacy-practices">We need to outcompete legacy practices</h2>

<p>The best way to make legacy practices obsolete is to outcompete them.</p>

<blockquote>
  <p>By using the latest technologies and a modern mindset, we will build <strong>better solutions</strong> faster.</p>
</blockquote>

<p>Solutions that are:</p>

<ul>
  <li>Easy to maintain</li>
  <li>Prepared for evolution</li>
  <li>Easy to extend</li>
  <li>Easy to support</li>
  <li>Ready for BC’s update cycle</li>
</ul>

<p>Reach out if you need help with the transition.</p>

<p>Finn Pedersen</p>

<p>Modern BC Developer</p>

<hr />

<blockquote>
  <p>PS: I realize that some people might be offended by this post. That was not the intention. The purpose of this post is not to offend anyone, but to create enough sense of urgency. The technical reality is that BC’s update cycle requires modern practices. The longer we delay, the more expensive and difficult the transition becomes. I want to help, not criticize.</p>
</blockquote>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[The urgent need to modernize Business Central development]]></summary></entry><entry><title type="html">Vibe Coding with Lovable.dev (No Code)</title><link href="https://www.finnpedersenfrance.com/programming/2025/10/15/vibe-coding-with-lovable-dev.html" rel="alternate" type="text/html" title="Vibe Coding with Lovable.dev (No Code)" /><published>2025-10-15T07:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/10/15/vibe-coding-with-lovable-dev</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/10/15/vibe-coding-with-lovable-dev.html"><![CDATA[<h2 id="vibe-coding-with-lovabledev">Vibe Coding with Lovable.dev</h2>

<h2 id="an-app-says-more-than-a-1000-words">An app says more than a 1000 words</h2>

<p><strong>If you are not a developer, you have to try Lovable.dev!</strong></p>

<p>The reason is that it will expand your view on what is possible.</p>

<p>This is what you will learn from making apps with <code class="language-plaintext highlighter-rouge">Lovable.dev</code>.</p>

<ul>
  <li>How to express an idea</li>
  <li>How to reformulate and clarify your thoughts</li>
  <li>How to communicate your idea to others</li>
  <li>Understand how your first hypotheses had flaws and can be improved</li>
</ul>

<p>To help you get started, there are some examples below.</p>

<h2 id="lovabledev">Lovable.dev</h2>

<p>Use my referral link to get free credits when you sign up.</p>

<p><a href="https://lovable.dev/invite/8e75e0e5-5de0-4a7f-860d-3f192c0714f1">https://lovable.dev/invite/8e75e0e5-5de0-4a7f-860d-3f192c0714f1</a></p>

<p><em>And don’t forget to publish your app.</em>
It will give me 10 credits to build more fun games.</p>

<h2 id="team-building">Team Building</h2>

<p><strong>I recommend vibe coding for team building.</strong></p>

<p>People who don’t know how to code often have fewer ideas for what is possible to make. No longer.</p>

<p>After a few attempts you will rapidly get the hang of it.</p>

<h2 id="the-hottest-new-programming-language-is-english">The hottest new programming language is English</h2>

<p>I will be giving a talk on Vibe Coding at the <strong>Directions EMEA 2025</strong> conference, 4-6 November 2025 in Poznan, Poland.</p>

<p>More information about the conference at <a href="https://www.directionsforpartners.com/emea2025">www.directionsforpartners.com/emea2025</a>
and my <a href="https://sessionize.com/s/finn-pedersen/vibe-coding.-the-hottest-new-programming-language-/142123">session</a>.</p>

<h2 id="snake-game">Snake Game</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Build a Snake game for my phone as a web app
</code></pre></div></div>

<h3 id="extra-instructions">Extra instructions</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The game must not end. When the snake reaches the edge it should just continue on the other side. Start with a length of 3. Add buttons on the right hand side. The swiping up and right did not work as expected.

Make the head of the snake blue.
</code></pre></div></div>

<p><a href="https://mobile-snake-play.lovable.app">mobile-snake-play.lovable.app</a></p>

<p><img src="/assets/images-01/LovableSnakeGame.png" alt="Snake Game" /></p>

<h2 id="converting-french-to-braille">Converting French to Braille</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Make a small web app that converts a text as I type it to braille. I want the braille to be above the text input field so I can see it as I type on my phone. The braille should not be abbreviated. I expect to write in French. ...
</code></pre></div></div>

<p><a href="https://braille-ahead-typer.lovable.app">braille-ahead-typer.lovable.app</a></p>

<p><img src="/assets/images-01/LovableConvertBraille.png" alt="Braille Converter" /></p>

<h2 id="2048-game">2048 Game</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Make a 2048 game that runs on my phone.
</code></pre></div></div>

<h3 id="extra-instruction">Extra instruction</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Swiping works left, right, and up. Swipe down refreshes the page.
</code></pre></div></div>

<p><a href="https://twenty-forty-eight-slide.lovable.app/">twenty-forty-eight-slide.lovable.app</a></p>

<p><img src="/assets/images-01/Lovable2048game.png" alt="2048 Game" /></p>]]></content><author><name>Finn</name></author><category term="programming" /><summary type="html"><![CDATA[Vibe Coding with Lovable.dev]]></summary></entry><entry><title type="html">Vibe Coding with Cursor (Coding with AI)</title><link href="https://www.finnpedersenfrance.com/programming/2025/07/24/vibe-coding-with-cursor.html" rel="alternate" type="text/html" title="Vibe Coding with Cursor (Coding with AI)" /><published>2025-07-24T09:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/07/24/vibe-coding-with-cursor</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/07/24/vibe-coding-with-cursor.html"><![CDATA[<h2 id="vibe-coding-with-cursor">Vibe Coding with Cursor</h2>

<p>Whether we are just coding with AI or entirely Vibe Coding, we face the same challenges.</p>

<p>The term <strong>Vibe Coding</strong> went viral in February 2025 and now everyone is talking about it or trying it out.</p>

<p>I have submitted a talk, <strong>Vibe coding. The hottest new programming language is English</strong>
on this subject, to <a href="https://www.directionsforpartners.com/emea2025">Directions EMEA 2025</a></p>

<p><strong>There is actually a lot we can learn from vibe coding:</strong></p>

<ul>
  <li>How do we communicate our needs to the AI?</li>
  <li>Is that similar to how we communicate with a developer?</li>
  <li>What is easy and what is difficult in programming?</li>
  <li>Are some programming languages more suited for development with AI than others?</li>
  <li>Do you need to be a developer to do Vibe Coding?</li>
  <li>How much coding do you actually need to know to succeed with vibe coding?</li>
  <li>Is vibe coding just for fun or can I use it in my work?</li>
</ul>

<h2 id="what-is-vibe-coding">What is Vibe Coding?</h2>

<p>Computer scientist Andrej Karpathy, a co-founder of OpenAI and former AI leader at Tesla, introduced the term vibe coding in February 2025. The concept refers to a coding approach that relies on LLMs, allowing programmers to generate working code by providing natural language descriptions rather than manually writing it.</p>

<p>Source <a href="https://en.wikipedia.org/wiki/Vibe_coding">Wikipedia Vibe Coding</a></p>

<h2 id="the-snake-game">The snake game</h2>

<p>Here is an idea for a prompt:</p>

<blockquote>
  <p>Make the snake game in Elm as we knew it from the old nokia phones.</p>
</blockquote>

<ul>
  <li>Download and install Cursor</li>
  <li>Setup your development environment</li>
  <li>Write your prompt</li>
</ul>

<h3 id="40-minutes">40 minutes</h3>

<p>Within 40 minutes, I had a running game on my computer. I was super optimistic. Take into account that I was also playing, I mean testing, the game during that time.</p>

<p><img src="/assets/images-01/ElmSnakeGame.png" alt="Snake Game" /></p>

<p>Then …</p>

<h3 id="several-days-later">Several days later</h3>

<p>The app was only running on my laptop and I wanted it to work on my phone.
This is where the troubles started.
I refused to touch the code. I had to prompt my way to the result.
However, at one point, I had to study the code and specifically ask it to change some of the CSS.</p>

<p>I also had to rollback the code completely a couple of times.</p>

<p><img src="/assets/images-01/ElmSnakeGameMobileFirst.png" alt="Snake Game" /></p>

<p><a href="https://finnpedersenkazes.github.io/elm-snake/">Try it</a></p>

<blockquote>
  <p><strong>Warning</strong>: It is quite addictive.</p>
</blockquote>

<h3 id="project-on-github">Project on GitHub</h3>

<p><a href="https://github.com/finnpedersenkazes/elm-snake">Elm Snake Game on GitHub</a></p>

<p>The main branch is the first desktop version and the mobile-first branch is the mobile-first version.
Feel free to star the project and clone it.</p>

<p>You can follow my dialog with the AI in the <a href="https://github.com/finnpedersenkazes/elm-snake/blob/master/cursor_create_a_hello_world_app_in_elm.md">cursor_create_a_hello_world_app_in_elm.md</a> file.</p>

<p>It is the entire dialog to get to the first version. You don’t want to see the second. It is too long.</p>

<p>The <code class="language-plaintext highlighter-rouge">readme.md</code> is also almost entirely generated with a few prompts stating the sections.</p>

<h2 id="cursorcom">Cursor.com</h2>

<p>I used <a href="https://cursor.com/">Cursor.com</a>, the AI Code Editor, to realize the project.</p>

<p>I did not write a single line of code, so I did not care about setting anything up.</p>

<h2 id="programming-languages">Programming Languages</h2>

<p>Are all programming languages born equal for AI peer programming and Vibe Coding?
They are not. Actually, this will challenge our common worldview on programming languages.</p>

<p>What if another language will challenge <code class="language-plaintext highlighter-rouge">C#</code> or <code class="language-plaintext highlighter-rouge">TypeScript</code> simply because the AI works better with it?
Could a small and rather unknown language like <code class="language-plaintext highlighter-rouge">Elm</code> suddenly spring into popularity because it works much better with AI?</p>

<p>Some really interesting articles have started to appear.</p>

<ul>
  <li><a href="https://interjectedfuture.com/elm-as-target-language-for-vibe-coding/">Elm as target language for vibe coding by Wil Chung</a></li>
  <li><a href="https://medium.com/@jonathan.demontalembert/the-ai-symbiote-why-deterministic-languages-are-the-future-of-coding-f4a5ce4916e7">The AI Symbiote: Why Deterministic Languages Are the Future of Coding by Le Jon</a></li>
</ul>

<p>This is the reason why I chose Elm for my first vibe programming project.</p>

<h3 id="elm">Elm</h3>

<p>A delightful language for reliable web applications.</p>

<p><a href="https://elm-lang.org/">elm-lang.org</a></p>

<p>Elm is a pure functional language for building interactive web applications.
The model-update-view architecture has all state management handled in a single model.
The strong static type system guarantees <strong>no runtime errors</strong>.
And I just love the helpful compiler messages and easy-to-read code.</p>

<blockquote>
  <p>If it compiles, it works.</p>
</blockquote>

<p>As it turns out, the AI in Cursor also likes the helpful error messages from Elm.</p>

<h4 id="try-elm">Try Elm</h4>

<p>There are a couple of ways to try out Elm. Here are a few.</p>

<ul>
  <li><a href="https://elm-editor.com/">elm-editor.com</a></li>
  <li><a href="https://ellie-app.com/new">ellie-app.com</a></li>
</ul>

<h3 id="other-languages-and-al">Other languages and AL</h3>

<p>Since the amount of open source code for a given language is also important for training the LLM,
the most popular languages like JavaScript, TypeScript, and Python have an advantage.</p>

<p>A purpose-specific language might also have some advantages. AL is purpose-specific.</p>

<h4 id="al-and-ai">AL and AI</h4>

<p>Age and how much a language changes plays a role. AL is old and has evolved a lot.
There is no tradition for open source projects on GitHub, best practices were not followed, and
the language has evolved a lot. For example, we no longer write <code class="language-plaintext highlighter-rouge">Find('-')</code> we write <code class="language-plaintext highlighter-rouge">FindFirst()</code>.
So the LLM has to discard what was good in the past and now considered bad.</p>

<p>Where <code class="language-plaintext highlighter-rouge">Elm</code> has been stable for the past five years.</p>

<h2 id="ai-assistance-helping-the-ai">AI assistance helping the AI</h2>

<p>When I had trouble with the Mobile-First UI of the Game, I found that the Inspect function in
Chrome has an AI assistance. I asked how to fix the issue and passed the advice
directly to the AI developer in Cursor. Nice!</p>

<h2 id="conclusion">Conclusion</h2>

<p>Are you in a team?
Do you work with developers, project managers, product owners, users, or managers?</p>

<p><strong>Here is what you should do:</strong> Make a team-building event and vibe code something together.</p>

<p>You will learn much more than you could possibly imagine.</p>

<blockquote>
  <p><strong>Warning</strong>: You will have so much fun that you will have a hard time getting back to work.</p>
</blockquote>

<p>Have fun and learn</p>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[Vibe Coding with Cursor]]></summary></entry><entry><title type="html">Temporary Tables in Tests (Business Central)</title><link href="https://www.finnpedersenfrance.com/programming/2025/06/26/temporary-tables-in-tests.html" rel="alternate" type="text/html" title="Temporary Tables in Tests (Business Central)" /><published>2025-06-26T10:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/06/26/temporary-tables-in-tests</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/06/26/temporary-tables-in-tests.html"><![CDATA[<h2 id="temporary-tables-in-tests">Temporary Tables in Tests</h2>

<p>The theme for this series is <em>Super fast tests covering 100% of your code</em>.</p>

<p>This 2025 series of articles is a continuation of the 2024 series called the Clean Code Initiative.</p>

<p>Clean Code is a condition for writing good tests, but it does not guarantee code coverage by tests.</p>

<h2 id="scenario">Scenario</h2>

<p>The app we are building communicates with an external API.</p>

<p>We face several key requirements:</p>

<ul>
  <li>The app can only be in production mode when running in a production environment. If running in a sandbox, it must operate in test mode.</li>
  <li>Our tests must cover all code paths, including those only reachable in production mode.</li>
  <li>Our tests cannot call the actual API, but we still need to verify both the requests we send and how we decode the API responses.</li>
  <li>Tests must be extremely fast, with runtimes measured in milliseconds.</li>
  <li>Writing tests should be quick and cost-effective.</li>
  <li>We want to test only our own code, assuming that dependencies and external systems work as expected. We assume everyone else knows what they are doing</li>
</ul>

<blockquote>
  <p><strong>Temporary Tables</strong> will satisfy the need for speed and isolation from the database.</p>
</blockquote>

<h2 id="avoid-touching-the-database-in-tests">Avoid Touching the Database in Tests</h2>

<p>We have looked at Environment Interfaces and how they save us time and improve speed, but there is one more thing to avoid during tests: touching the database. Here, we have to think differently—we are not going to develop an interface toward the database, and we don’t have to.</p>

<p>If we want to run hundreds of tests in seconds, we have to change the way we write functions. While the Clean Code Initiative told you to stick to the standard patterns, we are now going to challenge this a little bit in some cases.</p>

<h2 id="pure-functions">Pure Functions</h2>

<p>First of all, we want to have <strong>pure functions</strong>—that is, functions with no side effects. Touching the database is a side effect.</p>

<p>This has important consequences for where we place database operations like <code class="language-plaintext highlighter-rouge">Record.Get()</code>, <code class="language-plaintext highlighter-rouge">Record.Modify()</code>, <code class="language-plaintext highlighter-rouge">Record.Insert()</code>, and <code class="language-plaintext highlighter-rouge">Record.FindSet()</code> in our code. In this project, they are now all at the top level.</p>

<h2 id="passing-records-by-reference">Passing Records by Reference</h2>

<p>To achieve fast, isolated tests, we use <strong>temporary tables</strong>, which run entirely in memory. When a function operates on a temporary record, you must pass that record <strong>by reference</strong> using the <code class="language-plaintext highlighter-rouge">var</code> keyword. This ensures that any changes made to the record inside the function are retained and that the function works with the same in-memory instance. In the example below, both <code class="language-plaintext highlighter-rouge">EncodePhoneNumber</code> and the helper method <code class="language-plaintext highlighter-rouge">GetPhoneNumber</code> take <code class="language-plaintext highlighter-rouge">Setup</code> as a <code class="language-plaintext highlighter-rouge">var</code> parameter, allowing the test to run against a temporary, in-memory version of the table instead of querying or modifying the actual database.</p>

<pre><code class="language-al">internal procedure EncodePhoneNumber(var Setup: Record Setup; ObjectKey: Text; PhoneNumber: Text; var Object: JsonObject)
var
    PhoneNumberAsText: Text;
begin
    PhoneNumberAsText := this.GetPhoneNumber(Setup, PhoneNumber);
    Object.Add(ObjectKey, PhoneNumberAsText);
end;
</code></pre>

<pre><code class="language-al">local procedure GetPhoneNumber(var Setup: Record Setup; PhoneNumber: Text): Text
begin
    if Setup."Application State" &lt;&gt; Enum::"Application State"::Production then
        exit(Setup."Phone Number Test");
    exit(PhoneNumber);
end;
</code></pre>

<p>Previously, I would simply have made a <code class="language-plaintext highlighter-rouge">Setup.Get()</code> at the bottom level where I needed the information.
This new approach forces me to have a well-organized architecture of my code.</p>

<h3 id="testing-in-the-context-of-a-production-environment">Testing in the context of a production environment</h3>

<pre><code class="language-al">[Test]
internal procedure TestEncodePhoneNumberProduction()
var
    TempSetup: Record Setup temporary;
    Decoders: Codeunit Decoders;
    Encoders: Codeunit Encoders;
    Object: JsonObject;
    CalculatedPhoneNumber: Text;
    CustomerPhoneNumber: Text;
begin
    // [SCENARIO #015] Testing encoding a phone number to a JSON object in a production environment.
    // [GIVEN] A phone number
    // [WHEN] encoded to JSON object
    // [THEN] we should get the same phone number

    this.TestHelper.InitializeSetup(TempSetup);
    TempSetup."Application State" := Enum::"Application State"::Production;
    CustomerPhoneNumber := '1234567890';
    Encoders.EncodePhoneNumber(TempSetup, 'Test', CustomerPhoneNumber, Object);
    Decoders.DecodeText(Object, 'Test', CalculatedPhoneNumber);
    this.Assert.AreEqual(CustomerPhoneNumber, CalculatedPhoneNumber, 'In the production environment we expected to encode the customer phone number directly.');
end;
</code></pre>

<h3 id="testing-in-the-context-of-a-test-environment">Testing in the context of a test environment</h3>

<pre><code class="language-al">[Test]
internal procedure TestEncodePhoneNumberTest()
var
    TempSetup: Record Setup temporary;
    Decoders: Codeunit Decoders;
    Encoders: Codeunit Encoders;
    Object: JsonObject;
    CalculatedPhoneNumber: Text;
    CustomerPhoneNumber: Text;
begin
    // [SCENARIO #016] Testing encoding a phone number to a JSON object in a test environment.
    // [GIVEN] A phone number
    // [WHEN] encoded to JSON object
    // [THEN] we should get the test phone number

    this.TestHelper.InitializeSetup(TempSetup);
    TempSetup."Application State" := Enum::"Application State"::Test;
    TempSetup."Phone Number Test" := '1234567890';
    CustomerPhoneNumber := '0987654321';
    Encoders.EncodePhoneNumber(TempSetup, 'Test', CustomerPhoneNumber, Object);
    Decoders.DecodeText(Object, 'Test', CalculatedPhoneNumber);
    this.Assert.AreEqual(TempSetup."Phone Number Test", CalculatedPhoneNumber, 'In the test environment we expected to ignore the customer phone number and encode the test phone number from the setup.');
end;
</code></pre>

<h3 id="running-the-actual-code">Running the actual code</h3>

<p>In the actual code, the <code class="language-plaintext highlighter-rouge">Setup.Get()</code> is executed once at the highest level. This also gives me a better understanding of all dependencies on the setup table in the code. That is, we do not retrieve the setup table in multiple places throughout the code. We get it once and pass it around by reference.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This new design has several advantages:</p>

<ul>
  <li><strong>Better performance:</strong> Fewer reads to the database.</li>
  <li><strong>Fast, isolated tests:</strong> Run tests without database access using just temporary tables.</li>
  <li><strong>Improved code overview:</strong> Dependencies are clear and easy to manage.</li>
</ul>

<p>Overall, this approach leads to a better design, even though it is not entirely how we used to write code. It is essential for writing modern, maintainable, and high-performance AL test code.</p>

<h2 id="clean-al-code-initiative">Clean AL Code Initiative</h2>

<ul>
  <li>Part 1: <a href="/programming/2023/10/10/BC-Rulesets.html">Rulesets in Business Central</a></li>
  <li>Part 2: <a href="/programming/2023/12/28/Namespaces-in-AL.html">Namespaces in AL</a></li>
  <li>Part 3: <a href="/programming/2023/12/29/Extensions-for-AL.html">VS Code Extensions for AL</a></li>
  <li>Part 4: <a href="/programming/2024/01/03/Automated-Tests-in-AL.html">Automated Tests in AL</a></li>
  <li>Part 5: <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced CodeCop Analyzer and Custom Rulesets</a></li>
  <li>Part 6: <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a></li>
  <li>Part 7: <a href="/programming/2024/01/12/TryFunctions-in-AL.html">Preconditions and TryFunctions in AL</a></li>
  <li>Part 8: <a href="/programming/2026/07/22/there-is-a-new-sheriff-in-town.html">There is a new sheriff in town</a></li>
</ul>

<h2 id="super-fast-tests-covering-100-of-your-code">Super fast tests covering 100% of your code</h2>

<ul>
  <li>Part 1: <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces - Part 1 - System Environment</a></li>
  <li>Part 2: <a href="/programming/2025/06/26/environment-interface-part-2.html">Environment Interfaces - Part 2 - External API</a></li>
  <li>Part 3: <a href="/programming/2025/06/26/environment-interface-part-3.html">Environment Interfaces - Part 3 - Standard Application</a></li>
  <li><strong>Part 4</strong>: <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a></li>
</ul>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[Temporary Tables in Tests]]></summary></entry><entry><title type="html">Environment Interfaces - Part 3 (Business Central)</title><link href="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-3.html" rel="alternate" type="text/html" title="Environment Interfaces - Part 3 (Business Central)" /><published>2025-06-26T09:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-3</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-3.html"><![CDATA[<h2 id="environment-interfaces---part-3">Environment Interfaces - Part 3</h2>

<p>The theme for this series is <em>Super fast tests covering 100% of your code</em>.</p>

<p>This 2025 series of articles is a continuation of the 2024 series called the Clean Code Initiative.</p>

<p>Clean Code is a condition for writing good tests, but it does not guarantee code coverage by tests.</p>

<h2 id="introduction">Introduction</h2>

<p>I will introduce three different contexts of Environment Interfaces. Discussed in parts 1, 2, and 3.</p>

<ol>
  <li><a href="/programming/2025/06/26/environment-interface-part-1.html">Interface against the system environment (Sandbox or Production)</a></li>
  <li><a href="/programming/2025/06/26/environment-interface-part-2.html">Interface against an external API</a></li>
  <li>Interface against the standard application</li>
</ol>

<blockquote>
  <p><strong>Environment interfaces</strong> usually have a single implementation within the application but can have several implementations in the test application. There is no corresponding <code class="language-plaintext highlighter-rouge">Enum</code> implementing the <code class="language-plaintext highlighter-rouge">Interface</code>.</p>
</blockquote>

<h2 id="scenario">Scenario</h2>

<p>The app we are building communicates with an external API.</p>

<p>We face several key requirements:</p>

<ul>
  <li>The app can only be in production mode when running in a production environment. If running in a sandbox, it must operate in test mode.</li>
  <li>Our tests must cover all code paths, including those only reachable in production mode.</li>
  <li>Our tests cannot call the actual API, but we still need to verify both the requests we send and how we decode the API responses.</li>
  <li>Tests must be extremely fast, with runtimes measured in milliseconds.</li>
  <li>Writing tests should be quick and cost-effective.</li>
  <li>We want to test only our own code, assuming that dependencies and external systems work as expected. We assume everyone else knows what they are doing</li>
</ul>

<blockquote>
  <p><strong>Environment Interfaces</strong> are the solution to this apparent paradox.</p>
</blockquote>

<h2 id="interface-against-standard-business-central">Interface against Standard Business Central</h2>

<p>In parts 1 and 2, we have seen how interfaces enable us to reach code we could otherwise never reach in tests.</p>

<p>However, there are other important constraints: we cannot spend too much time writing tests, and we only want to test our own code.</p>

<p>This is why we sometimes have to develop an interface against the standard application.</p>

<h3 id="finance-interface">Finance interface</h3>

<p>In this case, we are handling payments and refunds.</p>

<pre><code class="language-al">namespace MicrosoftPartner.AppName.Finance;

using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Sales.History;

interface IFinance
{
    procedure CreatePaymentLine(
        var PaymentRequest: Record "Payment Request";
        var Setup: Record Setup;
        var GenJournalBatch: Record "Gen. Journal Batch";
        var GenJournalLine: Record "Gen. Journal Line";
        var GenJournalTemplate: Record "Gen. Journal Template";
        var PostedSalesInvoiceHeader: Record "Sales Invoice Header")

    procedure CreateRefundLine(
        var RefundRequest: Record "Refund Request";
        var Setup: Record Setup;
        var GenJournalBatch: Record "Gen. Journal Batch";
        var GenJournalLine: Record "Gen. Journal Line";
        var GenJournalTemplate: Record "Gen. Journal Template";
        var SalesCrMemoHeader: Record "Sales Cr.Memo Header")
}
</code></pre>

<h3 id="implementing-the-app-version-of-the-interface">Implementing the App version of the interface</h3>

<p>The implementation is as you would expect. It is the code you would normally have written.
We are inserting a GenJournalLine and we are using <code class="language-plaintext highlighter-rouge">.Validate</code> to ensure that the line is created with all the logic on the fields</p>

<pre><code class="language-al">codeunit 50019 "App Finance" implements IFinance
{
    procedure CreatePaymentLine(...)
    begin
        GenJournalLine.Init();
        GenJournalLine.Validate("Journal Template Name", GenJournalTemplate.Name);
        GenJournalLine.Validate("Journal Batch Name", GenJournalBatch.Name);
        GenJournalLine.Validate("Document Type", Enum::"Gen. Journal Document Type"::Payment);
        GenJournalLine.Validate("Document No.", PaymentRequest."Request Reference");
        
        ...
        GenJournalLine.Insert(true);
    end;

    procedure CreateRefundLine(...)
    begin
        GenJournalLine.Init();
        ...
        GenJournalLine.Insert(true);
    end;
}
</code></pre>

<h3 id="implementing-the-test-app-version-of-the-interface">Implementing the Test App version of the interface</h3>

<p>This is where it becomes interesting.</p>

<h4 id="stub-finance">Stub Finance</h4>

<p>The stub function simply returns what I have asked it to return.
This is how I control which part of my code is touched.</p>

<pre><code class="language-al">namespace MicrosoftPartner.AppName.Test.Helpers;

using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Sales.History;

codeunit 50120 "Stub Finance" implements IFinance
{
    Description = 'Stub Finance';

    var
        SetupVar: Record Setup;
        GenJournalBatchVar: Record "Gen. Journal Batch";
        GenJournalLineVar: Record "Gen. Journal Line";
        GenJournalTemplateVar: Record "Gen. Journal Template";
        PaymentRequestVar: Record "Payment Request";
        RefundRequestVar: Record "Refund Request";
        PostedSalesCrMemoHeaderVar: Record "Sales Cr.Memo Header";
        PostedSalesInvoiceHeaderVar: Record "Sales Invoice Header";

    internal procedure CreatePaymentLine(var PaymentRequest: Record "Payment Request"; var Setup: Record Setup; var GenJournalBatch: Record "Gen. Journal Batch"; var GenJournalLine: Record "Gen. Journal Line"; var GenJournalTemplate: Record "Gen. Journal Template"; var PostedSalesInvoiceHeader: Record "Sales Invoice Header")
    begin
        PaymentRequest := this.PaymentRequestVar;
        Setup := this.SetupVar;
        GenJournalBatch := this.GenJournalBatchVar;
        GenJournalLine := this.GenJournalLineVar;
        GenJournalTemplate := this.GenJournalTemplateVar;
        PostedSalesInvoiceHeader := this.PostedSalesInvoiceHeaderVar;
    end;

    internal procedure SetupPaymentResponse(var PaymentRequest: Record "Payment Request"; var Setup: Record Setup; var GenJournalBatch: Record "Gen. Journal Batch"; var GenJournalLine: Record "Gen. Journal Line"; var GenJournalTemplate: Record "Gen. Journal Template"; var PostedSalesInvoiceHeader: Record "Sales Invoice Header")
    begin
        this.PaymentRequestVar := PaymentRequest;
        this.SetupVar := Setup;
        this.GenJournalBatchVar := GenJournalBatch;
        this.GenJournalLineVar := GenJournalLine;
        this.GenJournalTemplateVar := GenJournalTemplate;
        this.PostedSalesInvoiceHeaderVar := PostedSalesInvoiceHeader;
    end;

    internal procedure CreateRefundLine(var RefundRequest: Record "Refund Request"; var Setup: Record Setup; var GenJournalBatch: Record "Gen. Journal Batch"; var GenJournalLine: Record "Gen. Journal Line"; var GenJournalTemplate: Record "Gen. Journal Template"; var SalesCrMemoHeader: Record "Sales Cr.Memo Header")
    begin
        RefundRequest := this.RefundRequestVar;
        Setup := this.SetupVar;
        GenJournalBatch := this.GenJournalBatchVar;
        GenJournalLine := this.GenJournalLineVar;
        GenJournalTemplate := this.GenJournalTemplateVar;
        SalesCrMemoHeader := this.PostedSalesCrMemoHeaderVar;
    end;

    internal procedure SetupRefundResponse(var RefundRequest: Record "Refund Request"; var Setup: Record Setup; var GenJournalBatch: Record "Gen. Journal Batch"; var GenJournalLine: Record "Gen. Journal Line"; var GenJournalTemplate: Record "Gen. Journal Template"; var SalesCrMemoHeader: Record "Sales Cr.Memo Header")
    begin
        this.RefundRequestVar := RefundRequest;
        this.SetupVar := Setup;
        this.GenJournalBatchVar := GenJournalBatch;
        this.GenJournalLineVar := GenJournalLine;
        this.GenJournalTemplateVar := GenJournalTemplate;
        this.PostedSalesCrMemoHeaderVar := SalesCrMemoHeader;
    end;
}
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>Running logic in the base application introduces several challenges:</p>

<p>Setting up tests is time-consuming due to dependencies on G/L Accounts, Banks, General Journal Batch, General Journal Template, Number Series, and more — a rabbit hole that can consume significant amounts of time.</p>

<p>It creates a strong dependency on the base app, increasing maintenance overhead.</p>

<p>It is slow, as it writes to the database and rolls back changes, preventing the use of temporary variables.</p>

<p>By using Environment Interfaces, I have avoided these issues. My tests are fast, independent, and focused on my code.</p>

<p>You might say that this is cheating. That I am not really testing the code.
And you are right in the sense that I have to understand perfectly what Business Central is returning when it creates the General Journal Line. But I am not testing that Business Central is working; I am testing that my code is working.</p>

<h2 id="clean-al-code-initiative">Clean AL Code Initiative</h2>

<ul>
  <li>Part 1: <a href="/programming/2023/10/10/BC-Rulesets.html">Rulesets in Business Central</a></li>
  <li>Part 2: <a href="/programming/2023/12/28/Namespaces-in-AL.html">Namespaces in AL</a></li>
  <li>Part 3: <a href="/programming/2023/12/29/Extensions-for-AL.html">VS Code Extensions for AL</a></li>
  <li>Part 4: <a href="/programming/2024/01/03/Automated-Tests-in-AL.html">Automated Tests in AL</a></li>
  <li>Part 5: <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced CodeCop Analyzer and Custom Rulesets</a></li>
  <li>Part 6: <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a></li>
  <li>Part 7: <a href="/programming/2024/01/12/TryFunctions-in-AL.html">Preconditions and TryFunctions in AL</a></li>
  <li>Part 8: <a href="/programming/2026/07/22/there-is-a-new-sheriff-in-town.html">There is a new sheriff in town</a></li>
</ul>

<h2 id="super-fast-tests-covering-100-of-your-code">Super fast tests covering 100% of your code</h2>

<ul>
  <li>Part 1: <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces - Part 1 - System Environment</a></li>
  <li>Part 2: <a href="/programming/2025/06/26/environment-interface-part-2.html">Environment Interfaces - Part 2 - External API</a></li>
  <li><strong>Part 3</strong>: <a href="/programming/2025/06/26/environment-interface-part-3.html">Environment Interfaces - Part 3 - Standard Application</a></li>
  <li>Part 4: <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a></li>
</ul>

<h2 id="london-style-terminology">London Style Terminology</h2>

<p>When writing tests in Business Central, you may come across different types of test doubles—objects that replace real dependencies in your code. The “London style” (or “mockist”) terminology is widely used in the testing community to describe these patterns. Understanding these terms will help you communicate more clearly about your test design and choose the right approach for your scenario.</p>

<table>
  <thead>
    <tr>
      <th>Term</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dummy</strong></td>
      <td>An object passed as a parameter but never used. Only exists to fill parameter lists.</td>
    </tr>
    <tr>
      <td><strong>Stub</strong></td>
      <td>A fake object that returns fixed data and <strong>does not track usage</strong>.</td>
    </tr>
    <tr>
      <td><strong>Fake</strong></td>
      <td>A working implementation with simplified behavior (e.g., in-memory database).</td>
    </tr>
    <tr>
      <td><strong>Spy</strong></td>
      <td>Like a stub, but records how it was called for later verification.</td>
    </tr>
    <tr>
      <td><strong>Mock</strong></td>
      <td>A fake that <strong>also verifies</strong> how it was used (e.g., was method X called?).</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[Environment Interfaces - Part 3]]></summary></entry><entry><title type="html">Environment Interfaces - Part 2 (Business Central)</title><link href="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-2.html" rel="alternate" type="text/html" title="Environment Interfaces - Part 2 (Business Central)" /><published>2025-06-26T08:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-2</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-2.html"><![CDATA[<h2 id="environment-interfaces---part-2">Environment Interfaces - Part 2</h2>

<p>The theme for this series is <em>Super fast tests covering 100% of your code</em>.</p>

<p>This 2025 series of articles is a continuation of the 2024 series called the Clean Code Initiative.</p>

<p>Clean Code is a condition for writing good tests, but it does not guarantee code coverage by tests.</p>

<h2 id="introduction">Introduction</h2>

<p>I will introduce three different contexts of Environment Interfaces. Discussed in parts 1, 2, and 3.</p>

<ol>
  <li><a href="/programming/2025/06/26/environment-interface-part-1.html">Interface against the system environment (Sandbox or Production)</a></li>
  <li>Interface against an external API</li>
  <li><a href="/programming/2025/06/26/environment-interface-part-3.html">Interface against the standard application</a></li>
</ol>

<blockquote>
  <p><strong>Environment interfaces</strong> usually have a single implementation within the application but can have several implementations in the test application. There is no corresponding <code class="language-plaintext highlighter-rouge">Enum</code> implementing the <code class="language-plaintext highlighter-rouge">Interface</code>.</p>
</blockquote>

<h2 id="scenario">Scenario</h2>

<p>The app we are building communicates with an external API.</p>

<p>We face several key requirements:</p>

<ul>
  <li>The app can only be in production mode when running in a production environment. If running in a sandbox, it must operate in test mode.</li>
  <li>Our tests must cover all code paths, including those only reachable in production mode.</li>
  <li>Our tests cannot call the actual API, but we still need to verify both the requests we send and how we decode the API responses.</li>
  <li>Tests must be extremely fast, with runtimes measured in milliseconds.</li>
  <li>Writing tests should be quick and cost-effective.</li>
  <li>We want to test only our own code, assuming that dependencies and external systems work as expected. We assume everyone else knows what they are doing</li>
</ul>

<blockquote>
  <p><strong>Environment Interfaces</strong> are the solution to this apparent paradox.</p>
</blockquote>

<h2 id="interface-against-the-external-api">Interface against the external API</h2>

<p>Before describing the interface, I need a small helper Enum.</p>

<h3 id="api-method-enum">Api Method Enum</h3>

<p>There is a standard Enum, but I just want to keep it simple and to what I need.</p>

<pre><code class="language-al">enum 50005 "Api Method"
{
    value(0; GET)
    {
        Caption = 'GET', Locked = true;
    }
    value(1; POST)
    {
        Caption = 'POST', Locked = true;
    }
}
</code></pre>

<h3 id="api-request-interface">Api Request Interface</h3>

<p>It is a very simple interface with just one function <strong>Send</strong>.
But it is all we need.</p>

<pre><code class="language-al">interface IApiRequest
{
    procedure Send(RequestMethod: Enum "Api Method"; RequestUrl: Text; Payload: Text; SecretKey: SecretText; var ResponseStatusCode: Integer; var ResponseContent: Text)
}
</code></pre>

<h3 id="implementing-the-app-version-of-the-interface">Implementing the App version of the interface</h3>

<p>The implementation of the Send function is quite generic and you can reuse this code. The API accepts a JSON payload, an encrypted key, and it returns a Status Code (200 for OK) and a response payload also in JSON.</p>

<pre><code class="language-al">codeunit 50011 "App Api Request" implements IApiRequest
{
    internal procedure Send(RequestMethod: Enum "Api Method"; RequestUrl: Text; Payload: Text; SecretKey: SecretText; var ResponseStatusCode: Integer; var ResponseContent: Text)
    var
        ApiTools: Codeunit ApiTools;
        HttpClient: HttpClient;
        HttpContent: HttpContent;
        ContentHeaders: HttpHeaders;
        RequestHeaders: HttpHeaders;
        HttpRequestMessage: HttpRequestMessage;
        HttpResponseMessage: HttpResponseMessage;
        Hash: SecretText;
    begin
        Hash := ApiTools.CreateHash(Payload, SecretKey);

        HttpRequestMessage.Method := Format(RequestMethod);
        HttpRequestMessage.SetRequestUri(RequestUrl);
        HttpRequestMessage.GetHeaders(RequestHeaders);
        RequestHeaders.Clear();
        RequestHeaders.Add('Accept', 'application/json');
        RequestHeaders.Add('Authorization', Hash);

        if RequestMethod = Enum::"Api Method"::POST then begin
            HttpContent.WriteFrom(Payload);
            HttpContent.GetHeaders(ContentHeaders);
            ContentHeaders.Clear();
            ContentHeaders.Add('Content-Type', 'application/json');
            HttpRequestMessage.Content := HttpContent;
        end;

        if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin
            ResponseStatusCode := HttpResponseMessage.HttpStatusCode();
            if ResponseStatusCode = ApiTools.HttpStatusCodeOK() then
                HttpResponseMessage.Content().ReadAs(ResponseContent)
            else
                ApiTools.WebServiceCallFailedError(HttpResponseMessage.HttpStatusCode());
        end else
            ApiTools.ConnectionError();
    end;
}
</code></pre>

<h3 id="implementing-the-test-app-version-of-the-interface">Implementing the Test App version of the interface</h3>

<p>This is where it becomes interesting.</p>

<h4 id="stub-api-request">Stub Api Request</h4>

<p>The stub function simply returns what I have asked it to return.
This is how I control which part of my code is touched.</p>

<pre><code class="language-al">codeunit 50113 "Stub Api Request" implements IApiRequest
{
    Description = 'Stub Api Request';

    var
        ResponseStatusCodeVar: Integer;
        ResponseContentVar: Text;

    internal procedure Send(RequestMethod: Enum "Api Method"; RequestUrl: Text; Payload: Text; SecretKey: SecretText; var ResponseStatusCode: Integer; var ResponseContent: Text)
    begin
        ResponseStatusCode := this.ResponseStatusCodeVar;
        ResponseContent := this.ResponseContentVar;
    end;

    internal procedure SetupResponse(ResponseStatusCode: Integer; ResponseContent: Text)
    begin
        this.ResponseStatusCodeVar := ResponseStatusCode;
        this.ResponseContentVar := ResponseContent;
    end;
}
</code></pre>

<h3 id="using-it">Using it</h3>

<p>For each endpoint the API exposes, I have written a Codeunit with these functions</p>

<pre><code class="language-al">codeunit 50007 "Api Operation &lt;Endpointname&gt;"
{
    Description = 'Implementation of the Api Operation &lt;Endpointname&gt;';

    internal procedure OperationType(): Text
    begin
        exit('&lt;Endpointname&gt;');
    end;

    internal procedure RequestMethod(): Enum "Api Method"
    begin
        exit(Enum::"Api Method"::POST);
    end;

    internal procedure RequestPayload(var EndpointTable: Record "Endpoint Table Name"): Text[2048]
    begin
        exit(&lt;the calculated payload&gt;);
    end;

    internal procedure SendRequest(var Setup: Record Setup"; var EndpointTable: Record "Endpoint Table Name"; ApiRequest: Interface IApiRequest)
    var
        Decoders: Codeunit Decoders;
        ResponseStatusCode: Integer;
        SecretKey: SecretText;
        RequestUrl: Text;
        ResponseContent: Text;
    begin
        RequestUrl := Setup.ApiUrl(this.OperationType());
        SecretKey := Setup.ApiHmac();
        ResponseStatusCode := 0;
        ApiRequest.Send(this.RequestMethod(), RequestUrl, this.RequestPayload(EndpointTable), SecretKey, ResponseStatusCode, ResponseContent);
        Decoders.DecodeEndpointResponse(Setup, ResponseStatusCode, ResponseContent, EndpointTable);
    end;
}
</code></pre>

<h4 id="running-the-actual-code">Running the actual code</h4>

<p>When I call the SendRequest function, I pass it the implementation.</p>

<pre><code class="language-al">var
  AppApiRequest: Codeunit "App Api Request";
...

ApiOperationEndpoint.SendRequest(Setup, Rec, AppApiRequest);
</code></pre>

<h4 id="testing-in-the-context-of-a-fake-api">Testing in the context of a fake API</h4>

<p>Before calling <code class="language-plaintext highlighter-rouge">SendRequest</code>, I call the <code class="language-plaintext highlighter-rouge">SetupResponse</code> and tell my fake API what to return to me.
I know that this sounds strange at first, but trust me. I am not testing if the API works.
I am testing how my code reacts to the response from the API.
In this way, I can ensure that I am also testing more exotic events and responses.</p>

<p><strong>This is the setup for success:</strong></p>

<pre><code class="language-al">StubApiRequest.SetupResponse(200, '{"Result":"OK","Reference":"TEST"}');
ApiOperationEndpoint.SendRequest(TempSetup, TempRecord, StubApiRequest);
</code></pre>

<p><strong>Failure could look like this:</strong></p>

<pre><code class="language-al">StubApiRequest.SetupResponse(200, StubApiResponses.NotUniqueErrorResponse());
ApiOperationEndpoint.SendRequest(TempSetup, TempRecord, StubApiRequest);
</code></pre>

<p>I still get a valid JSON, but something is wrong. Then I am testing if my decoders are working as they should.</p>

<h2 id="conclusion">Conclusion</h2>

<p>By using stubs and environment interfaces, I can thoroughly test all my decoding logic, failure detection, and error handling — without ever calling the real API during tests.</p>

<p>This approach ensures that every line of my code is exercised and verified, resulting in fast, reliable, and maintainable tests for my Business Central app.</p>

<h2 id="clean-al-code-initiative">Clean AL Code Initiative</h2>

<ul>
  <li>Part 1: <a href="/programming/2023/10/10/BC-Rulesets.html">Rulesets in Business Central</a></li>
  <li>Part 2: <a href="/programming/2023/12/28/Namespaces-in-AL.html">Namespaces in AL</a></li>
  <li>Part 3: <a href="/programming/2023/12/29/Extensions-for-AL.html">VS Code Extensions for AL</a></li>
  <li>Part 4: <a href="/programming/2024/01/03/Automated-Tests-in-AL.html">Automated Tests in AL</a></li>
  <li>Part 5: <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced CodeCop Analyzer and Custom Rulesets</a></li>
  <li>Part 6: <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a></li>
  <li>Part 7: <a href="/programming/2024/01/12/TryFunctions-in-AL.html">Preconditions and TryFunctions in AL</a></li>
  <li>Part 8: <a href="/programming/2026/07/22/there-is-a-new-sheriff-in-town.html">There is a new sheriff in town</a></li>
</ul>

<h2 id="super-fast-tests-covering-100-of-your-code">Super fast tests covering 100% of your code</h2>

<ul>
  <li>Part 1: <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces - Part 1 - System Environment</a></li>
  <li><strong>Part 2</strong>: <a href="/programming/2025/06/26/environment-interface-part-2.html">Environment Interfaces - Part 2 - External API</a></li>
  <li>Part 3: <a href="/programming/2025/06/26/environment-interface-part-3.html">Environment Interfaces - Part 3 - Standard Application</a></li>
  <li>Part 4: <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a></li>
</ul>

<h2 id="london-style-terminology">London Style Terminology</h2>

<p>When writing tests in Business Central, you may come across different types of test doubles—objects that replace real dependencies in your code. The “London style” (or “mockist”) terminology is widely used in the testing community to describe these patterns. Understanding these terms will help you communicate more clearly about your test design and choose the right approach for your scenario.</p>

<table>
  <thead>
    <tr>
      <th>Term</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dummy</strong></td>
      <td>An object passed as a parameter but never used. Only exists to fill parameter lists.</td>
    </tr>
    <tr>
      <td><strong>Stub</strong></td>
      <td>A fake object that returns fixed data and <strong>does not track usage</strong>.</td>
    </tr>
    <tr>
      <td><strong>Fake</strong></td>
      <td>A working implementation with simplified behavior (e.g., in-memory database).</td>
    </tr>
    <tr>
      <td><strong>Spy</strong></td>
      <td>Like a stub, but records how it was called for later verification.</td>
    </tr>
    <tr>
      <td><strong>Mock</strong></td>
      <td>A fake that <strong>also verifies</strong> how it was used (e.g., was method X called?).</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[Environment Interfaces - Part 2]]></summary></entry><entry><title type="html">Environment Interfaces - Part 1 (Business Central)</title><link href="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-1.html" rel="alternate" type="text/html" title="Environment Interfaces - Part 1 (Business Central)" /><published>2025-06-26T07:00:00+02:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-1</id><content type="html" xml:base="https://www.finnpedersenfrance.com/programming/2025/06/26/environment-interface-part-1.html"><![CDATA[<h2 id="environment-interfaces---part-1">Environment Interfaces - Part 1</h2>

<p>The theme for this series is <em>Super fast tests covering 100% of your code</em>.</p>

<p>This 2025 series of articles is a continuation of the 2024 series called the Clean Code Initiative.</p>

<p>Clean Code is a prerequisite for writing good tests, but it does not guarantee code coverage by tests.</p>

<h2 id="introduction">Introduction</h2>

<p>I will introduce three different contexts of Environment Interfaces, discussed in parts 1, 2, and 3.</p>

<ol>
  <li>Interface against the system environment (Sandbox or Production)</li>
  <li><a href="/programming/2025/06/26/environment-interface-part-2.html">Interface against an external API</a></li>
  <li><a href="/programming/2025/06/26/environment-interface-part-3.html">Interface against the standard application</a></li>
</ol>

<blockquote>
  <p><strong>Environment interfaces</strong> usually have a single implementation within the application but can have several implementations in the test application. There is no corresponding <code class="language-plaintext highlighter-rouge">Enum</code> implementing the <code class="language-plaintext highlighter-rouge">Interface</code>.</p>
</blockquote>

<h2 id="scenario">Scenario</h2>

<p>The app we are building communicates with an external API.</p>

<p>We face several key requirements:</p>

<ul>
  <li>The app can only be in production mode when running in a production environment. If running in a sandbox, it must operate in test mode.</li>
  <li>Our tests must cover all code paths, including those only reachable in production mode.</li>
  <li>Our tests cannot call the actual API, but we still need to verify both the requests we send and how we decode the API responses.</li>
  <li>Tests must be extremely fast, with runtimes measured in milliseconds.</li>
  <li>Writing tests should be quick and cost-effective.</li>
  <li>We want to test only our own code, assuming that dependencies and external systems work as expected. We assume everyone else knows what they are doing</li>
</ul>

<blockquote>
  <p><strong>Environment Interfaces</strong> are the solution to this apparent paradox.</p>
</blockquote>

<h2 id="interface-against-the-system-environment">Interface against the system environment</h2>

<p>You can find the code for this article on GitHub.</p>

<p><a href="https://github.com/finnpedersenfrance/BC-Environment-Interfaces">github.com/finnpedersenfrance/BC-Environment-Interfaces</a></p>

<h3 id="system-environment-enum">System Environment Enum</h3>

<p>First, we need a new Enum. If you are running BC in the cloud, there are only two options. You are either in a
<strong>Sandbox</strong> or a <strong>Production</strong> environment.</p>

<pre><code class="language-al">enum 50004 "System Environment"
{
    value(0; " ")
    {
        Caption = ' ', Locked = true;
    }
    value(1; Sandbox)
    {
        Caption = 'Sandbox';
    }
    value(2; Production)
    {
        Caption = 'Production';
    }
}
</code></pre>

<h3 id="environment-interface">Environment Interface</h3>

<p>Then we need an interface.</p>

<p>The interface is the recipe for which functions need to be implemented.</p>

<pre><code class="language-al">interface IEnvironment
{
    /// &lt;summary&gt;
    /// Returns the state of the current system environment. That is either Production, Sandbox or empty.
    /// &lt;/summary&gt;
    /// &lt;param&gt;&lt;/param&gt;
    /// &lt;returns&gt;System Environment&lt;/returns&gt;
    procedure SystemEnvironment(): Enum "System Environment"

    /// &lt;summary&gt;
    /// Returns the name of the current company.
    /// &lt;/summary&gt;
    /// &lt;param&gt;&lt;/param&gt;
    /// &lt;returns&gt;Company Name&lt;/returns&gt;
    procedure ThisCompanyName(): Text[30]

    /// &lt;summary&gt;
    /// Returns the state of the current company. That is either Production or Evaluation.
    /// &lt;/summary&gt;
    /// &lt;param&gt;&lt;/param&gt;
    /// &lt;returns&gt;False if Production Company. True if Evaluation Company.&lt;/returns&gt;
    procedure IsEvaluationCompany(): Boolean
}
</code></pre>

<h3 id="implementing-the-app-version-of-the-interface">Implementing the App version of the interface</h3>

<p>The implementation is as you would expect. It is the code you would normally have written.</p>

<pre><code class="language-al">codeunit 50010 "App Environment" implements IEnvironment
{
    internal procedure SystemEnvironment(): Enum "System Environment"
    var
        EnvironmentInformation: Codeunit System.Environment."Environment Information";
    begin
        if EnvironmentInformation.IsProduction() then
            exit(Enum::"System Environment"::Production);
        if EnvironmentInformation.IsSandbox() then
            exit(Enum::"System Environment"::Sandbox);
        exit(Enum::"System Environment"::" ");
    end;

    internal procedure ThisCompanyName(): Text[30]
    begin
        exit(CopyStr(CompanyName(), 1, 30));
    end;

    internal procedure IsEvaluationCompany(): Boolean
    var
        Company: Record System.Environment.Company;
    begin
        Company.Get(CompanyName());
        exit(Company."Evaluation Company");
    end;
}
</code></pre>

<p>This also allows us to replace the booleans with an enum, which is much nicer.</p>

<blockquote>
  <p>Notice that <code class="language-plaintext highlighter-rouge">ThisCompanyName()</code> also solves the <strong>CodeCop Warning AA0139</strong> (overflow). <code class="language-plaintext highlighter-rouge">CompanyName()</code> returns a Text which causes an overflow warning when you assign it to a <code class="language-plaintext highlighter-rouge">Company Name</code> field of type <strong>Text[30]</strong>. With this interface function, I have solved this everywhere I needed to use <code class="language-plaintext highlighter-rouge">CompanyName()</code>.</p>
</blockquote>

<h3 id="implementing-the-test-app-version-of-the-interface">Implementing the Test App version of the interface</h3>

<p>This is where it becomes interesting.</p>

<h4 id="stub-production">Stub Production</h4>

<p>I have written a Stub Production implementation of the environment.
This allows me to fool my code into believing that it is in production.</p>

<pre><code class="language-al">codeunit 50102 "Stub Production Environment" implements IEnvironment
{
    Description = 'Stub Production Environment';

    procedure SystemEnvironment(): Enum "System Environment"
    begin
        exit(Enum::"System Environment"::Production);
    end;

    procedure ThisCompanyName(): Text[30]
    begin
        exit('Production Company Name');
    end;

    procedure IsEvaluationCompany(): Boolean
    begin
        exit(false);
    end;
}
</code></pre>

<blockquote>
  <p>Take a minute to look at how elegantly this allows us to test our code as if we were live in a production environment.</p>
</blockquote>

<h4 id="stub-test">Stub Test</h4>

<p>And a Stub Test implementation of the environment.</p>

<pre><code class="language-al">codeunit 50103 "Stub Test Environment" implements IEnvironment
{
    Description = 'Stub Test Environment';

    procedure SystemEnvironment(): Enum "System Environment"
    begin
        exit(Enum::"System Environment"::Sandbox);
    end;

    procedure ThisCompanyName(): Text[30]
    begin
        exit('Test Company Name');
    end;

    procedure IsEvaluationCompany(): Boolean
    begin
        exit(true);
    end;
}
</code></pre>

<blockquote>
  <p>And now we are in an evaluation company in a sandbox.</p>
</blockquote>

<h3 id="using-it">Using it</h3>

<p>It is crucial for my application code to know if the application is <strong>Production</strong> or in a <strong>Test</strong> setup.
For this, I am using an <code class="language-plaintext highlighter-rouge">enum</code> Application State.</p>

<pre><code class="language-al">enum 50006 "Application State"
{
    Extensible = true;

    value(0; " ")
    {
        Caption = ' ', Locked = true;
    }
    value(1; Test)
    {
        Caption = 'Test';
    }
    value(2; Production)
    {
        Caption = 'Production';
    }
}
</code></pre>

<p>I have written a function to calculate the state of the application based on the environment and the setup. The functions are in my Setup table.</p>

<pre><code class="language-al">internal procedure ValidateApplicationState(Environment: Interface IEnvironment)
begin
    Rec."Application State" := this.ApplicationState(Rec, Environment);
end;

internal procedure ApplicationState(var Setup: Record Setup; Environment: Interface IEnvironment): Enum "Application State"
begin
    case true of
        (Setup.Licensee = Environment.ThisCompanyName()) and
        (Environment.SystemEnvironment() = Enum::"System Environment"::Production) and
        (not Environment.IsEvaluationCompany()):
            exit(Enum::"Application State"::Production);
        (Setup.Licensee = Environment.ThisCompanyName()):
            exit(Enum::"Application State"::Test);
        else
            exit(Enum::"Application State"::" ");
    end;
end;
</code></pre>

<p>We are in <strong>production mode</strong>, only and only if</p>

<ul>
  <li>the app is in a Production environment and</li>
  <li>it is not an evaluation company, and</li>
  <li>the Licensee matches the name of the current company</li>
</ul>

<p>We are in a <strong>test mode</strong> if</p>

<ul>
  <li>the solution is running in a sandbox or</li>
  <li>the company is an evaluation company</li>
  <li>the Licensee still has to match the name of the current company</li>
</ul>

<h4 id="an-important-safety-feature">An important safety feature</h4>

<p>If someone makes a copy of the company, the company name will differ from the Licensee name in our setup, which will automatically disable the solution and prevent anyone from using the solution by mistake.</p>

<p>Now I can call this function in both my tests and production, reaching all the code.</p>

<h4 id="testing-in-the-context-of-a-test-environment">Testing in the context of a test environment</h4>

<p>Testing that the application is in test mode in a sandbox.</p>

<pre><code class="language-al">[Test]
internal procedure TestTestMode()
var
    TempSetup: Record Setup temporary;
    StubTestEnvironment: Codeunit "Stub Test Environment";
begin
    // [SCENARIO #003] Application State in test
    // [GIVEN] a setup with a valid licensee
    // [WHEN] calling ValidateApplicationState
    // [THEN] the application state should be test

    this.TestHelper.InitializeSetup(TempSetup);
    TempSetup.Licensee := StubTestEnvironment.ThisCompanyName();
    TempSetup.ValidateApplicationState(StubTestEnvironment);

    this.Assert.AreEqual(Enum::"Application State"::Test, TempSetup."Application State", 'Expected Application State to be Test.');
end;
</code></pre>

<h4 id="testing-in-the-context-of-a-production-environment">Testing in the context of a production environment</h4>

<p>Testing in our simulated production environment.</p>

<pre><code class="language-al">[Test]
internal procedure TestProductionMode()
var
    TempSetup: Record Setup temporary;
    StubProductionEnvironment: Codeunit "Stub Production Environment";
begin
    // [SCENARIO #002] Application State in production
    // [GIVEN] a setup with a valid licensee
    // [WHEN] calling ValidateApplicationState
    // [THEN] the application state should be production

    this.TestHelper.InitializeSetup(TempSetup);
    TempSetup.Licensee := StubProductionEnvironment.ThisCompanyName();
    TempSetup.ValidateApplicationState(StubProductionEnvironment);

    this.Assert.AreEqual(Enum::"Application State"::Production, TempSetup."Application State", 'Expected Application State to be Production.');
end;
</code></pre>

<p>Testing that the solution is disabled if there is no licensee.</p>

<pre><code class="language-al">[Test]
internal procedure TestNoCompanyName()
var
    TempSetup: Record Setup temporary;
    StubProductionEnvironment: Codeunit "Stub Production Environment";
begin
    // [SCENARIO #001] No company name in disabled environment
    // [GIVEN] a setup with no licensee
    // [WHEN] calling ValidateApplicationState
    // [THEN] the application state should be empty. I.e. disabled.

    this.TestHelper.InitializeSetup(TempSetup);
    TempSetup.Licensee := '';
    TempSetup.ValidateApplicationState(StubProductionEnvironment);

    this.Assert.AreEqual(Enum::"Application State"::" ", TempSetup."Application State", 'Expected Application State to be disabled.');
end;

</code></pre>

<h4 id="the-code-in-the-app">The code in the App</h4>

<p>I am not testing the following code at the highest level. I call the function from the Setup page.</p>

<pre><code class="language-al">trigger OnModifyRecord(): Boolean
var
    AppEnvironment: Codeunit "App Environment";
begin
    Rec.ValidateApplicationState(AppEnvironment);
end;
</code></pre>

<h2 id="bonus-safety-feature">Bonus Safety feature</h2>

<p>You can implement one more safety feature. Just to be sure that you solution is disabled in the copied company.
Subscribe to the <code class="language-plaintext highlighter-rouge">OnAfterCopyCompanyOnAction</code> event.</p>

<pre><code class="language-al">codeunit 50031 "Event Subscribers Copy Company"
{
    Description = 'Event Subscribers for Copy Company. This ensures that the copied company cannot be used by mistake.';

    [EventSubscriber(ObjectType::Page, Page::Companies, OnAfterCopyCompanyOnAction, '', false, false)]
    local procedure OnAfterCopyCompanyOnAction(CompanyName: Text[30])
    var
        Setup: Record Setup;
    begin
        Setup.ChangeCompany(CompanyName);
        if Setup.Get() then begin
            Setup.Licensee := '';
            Setup."Application State" := Enum::"Application State"::" ";
            Setup."Job Scheduler on Standby" := true;
            Setup.Modify(false);
        end;
    end;
}
</code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>Bugs are often found in code that was never executed before. Think about it. If I had a bug in the part of my code that would only be reached in production, I would otherwise never find it during tests.</p>

<p>With this design, I am quite confident that my code will act as planned when put in production and my tests cover 100% of my code.</p>

<h2 id="clean-al-code-initiative">Clean AL Code Initiative</h2>

<ul>
  <li>Part 1: <a href="/programming/2023/10/10/BC-Rulesets.html">Rulesets in Business Central</a></li>
  <li>Part 2: <a href="/programming/2023/12/28/Namespaces-in-AL.html">Namespaces in AL</a></li>
  <li>Part 3: <a href="/programming/2023/12/29/Extensions-for-AL.html">VS Code Extensions for AL</a></li>
  <li>Part 4: <a href="/programming/2024/01/03/Automated-Tests-in-AL.html">Automated Tests in AL</a></li>
  <li>Part 5: <a href="/programming/2024/01/05/BC-Advanced-Rulesets.html">Advanced CodeCop Analyzer and Custom Rulesets</a></li>
  <li>Part 6: <a href="/programming/2024/01/11/how-to-make-a-code-review.html">How to make a code review</a></li>
  <li>Part 7: <a href="/programming/2024/01/12/TryFunctions-in-AL.html">Preconditions and TryFunctions in AL</a></li>
  <li>Part 8: <a href="/programming/2026/07/22/there-is-a-new-sheriff-in-town.html">There is a new sheriff in town</a></li>
</ul>

<h2 id="super-fast-tests-covering-100-of-your-code">Super fast tests covering 100% of your code</h2>

<ul>
  <li><strong>Part 1</strong>: <a href="/programming/2025/06/26/environment-interface-part-1.html">Environment Interfaces - Part 1 - System Environment</a></li>
  <li>Part 2: <a href="/programming/2025/06/26/environment-interface-part-2.html">Environment Interfaces - Part 2 - External API</a></li>
  <li>Part 3: <a href="/programming/2025/06/26/environment-interface-part-3.html">Environment Interfaces - Part 3 - Standard Application</a></li>
  <li>Part 4: <a href="/programming/2025/06/26/temporary-tables-in-tests.html">Temporary Tables in Tests</a></li>
</ul>

<h2 id="london-style-terminology">London Style Terminology</h2>

<p>When writing tests in Business Central, you may come across different types of test doubles—objects that replace real dependencies in your code. The “London style” (or “mockist”) terminology is widely used in the testing community to describe these patterns. Understanding these terms will help you communicate more clearly about your test design and choose the right approach for your scenario.</p>

<table>
  <thead>
    <tr>
      <th>Term</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dummy</strong></td>
      <td>An object passed as a parameter but never used. Only exists to fill parameter lists.</td>
    </tr>
    <tr>
      <td><strong>Stub</strong></td>
      <td>A fake object that returns fixed data and <strong>does not track usage</strong>.</td>
    </tr>
    <tr>
      <td><strong>Fake</strong></td>
      <td>A working implementation with simplified behavior (e.g., in-memory database).</td>
    </tr>
    <tr>
      <td><strong>Spy</strong></td>
      <td>Like a stub, but records how it was called for later verification.</td>
    </tr>
    <tr>
      <td><strong>Mock</strong></td>
      <td>A fake that <strong>also verifies</strong> how it was used (e.g., was method X called?).</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Finn</name></author><category term="Programming" /><summary type="html"><![CDATA[Environment Interfaces - Part 1]]></summary></entry><entry><title type="html">How to Contribute to Wikipedia</title><link href="https://www.finnpedersenfrance.com/technology/2025/01/17/how-to-contribute-to-wikipedia.html" rel="alternate" type="text/html" title="How to Contribute to Wikipedia" /><published>2025-01-17T06:00:00+01:00</published><updated>2026-07-22T07:00:00+02:00</updated><id>https://www.finnpedersenfrance.com/technology/2025/01/17/how-to-contribute-to-wikipedia</id><content type="html" xml:base="https://www.finnpedersenfrance.com/technology/2025/01/17/how-to-contribute-to-wikipedia.html"><![CDATA[<h2 id="how-to-contribute-to-wikipedia--my-experience">How to Contribute to Wikipedia — My Experience</h2>

<p>I recently took a small detour from my usual topics to write my first Wikipedia article.</p>

<p>I have been renting out apartments in Denmark for more than 30 years and found myself searching for some information that dated back before 2001.</p>

<p>I wanted to share the information because</p>

<ul>
  <li>It was hard to find</li>
  <li>It was of general interest for both parties when renting an apartment, a room, or a house in Denmark.</li>
  <li>It potentially concerns a lot of people. Basically, all landlords and tenants.</li>
</ul>

<p>Since it was missing from Wikipedia, I decided to fill that gap. Here is a short guide, based on my experience, on how you can do it too.</p>

<h2 id="my-article-only-in-danish">My article (only in Danish)</h2>

<ul>
  <li><a href="https://da.wikipedia.org/wiki/Standardblanket_for_lejeaftaler">Wikipedia: Standardblanket for lejeaftaler</a></li>
</ul>

<p>In short, the article helps landlords and tenants to use the right version of the rental contract.
Over the past 29 years, there have been 4 versions, and I explain the importance of using the right version and
the potential consequences when you don’t.</p>

<h2 id="preparation">Preparation</h2>

<h3 id="check-if-your-topic-is-missing-or-underdeveloped">Check if your topic is missing or underdeveloped</h3>

<p>Before creating an article, do a quick search on Wikipedia to make sure it doesn’t already exist or that it’s not adequately covered.</p>

<h3 id="read-the-key-guidelines">Read the key guidelines</h3>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view">Wikipedia:Neutral Point of View</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style">Wikipedia:Manual of Style</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Wikipedia:Five_pillars">Wikipedia:Five Pillars</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Wikipedia:Notability">Wikipedia:Notability</a></li>
</ul>

<p>There is a lot to read and it took me some time.
Most is common sense, but I had extra focus on ensuring the <strong>Neutral Point of View</strong>.
I also wanted to make sure that it looked like a real Wikipedia article. That is, ensure consistency.</p>

<h3 id="gather-reliable-sources">Gather reliable sources</h3>

<p>Every fact, statement, or claim should ideally have a reference from a reputable source. Wikipedia articles thrive on verifiability.</p>

<p>Most of my sources were government websites, so that was easy.</p>

<h2 id="writing-your-draft">Writing Your Draft</h2>

<p>Once you have signed up, you go to your Sandbox and start writing.</p>

<h3 id="use-the-wikipedia-sandbox">Use the Wikipedia Sandbox</h3>

<p>You draft your article in the personal sandbox to get familiar with Wikipedia’s markup and style before publishing.</p>

<h3 id="focus-on-neutrality">Focus on neutrality</h3>

<p>Wikipedia articles must avoid personal opinions. State the facts, cite sources, and keep a neutral tone.</p>

<p>I made sure that both the landlord and the tenant would benefit equally from the article.</p>

<h3 id="wikipedia-markup">Wikipedia markup</h3>

<p>Wikipedia has a special markup language, which is different from, for example, Markdown.
But you don’t really have to know anything about it. Simply use the editor.</p>

<p>Here are a few examples.</p>

<ul>
  <li>Use double square brackets for internal links, e.g., <code class="language-plaintext highlighter-rouge">[[Article Title]]</code>.</li>
  <li>Bold the article title the first time it appears, e.g., <code class="language-plaintext highlighter-rouge">'''Article Title'''</code>.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">== Heading ==</code> syntax for section titles.</li>
</ul>

<h2 id="publishing-and-follow-up">Publishing and Follow-Up</h2>

<p>Once you have moved your article from the Sandbox to its real place, it is no longer your baby.
It lives its own life, and other people can contribute to it.</p>

<h3 id="review-your-draft">Review your draft</h3>

<p>Double-check grammar, style, and references. Ensure your draft aligns with
Wikipedia’s <a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style">Manual of Style</a>.</p>

<h3 id="move-from-sandbox-to-main-space">Move from sandbox to main space</h3>

<p>Once you’re confident, you can publish your draft. Be prepared for feedback and possible edits from the Wikipedia community.</p>

<h3 id="monitor-and-respond">Monitor and respond</h3>

<p>Other editors may add, remove, or refine content. Engage politely on the article’s talk page if you need to discuss changes.</p>

<h2 id="why-contribute">Why Contribute?</h2>

<h3 id="share-knowledge">Share knowledge</h3>

<p>If you notice a topic of general interest that isn’t on Wikipedia or is incomplete, adding your expertise benefits everyone.</p>

<h3 id="strengthen-wikipedia">Strengthen Wikipedia</h3>

<p>By contributing high-quality articles, you help maintain and improve the world’s largest encyclopedia.</p>

<h3 id="learn-along-the-way">Learn along the way</h3>

<p>Writing in Wikipedia’s style forces you to be concise, neutral, and well-referenced — excellent skills for any research-based writing.</p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>Contributing to Wikipedia was hard work, fun, rewarding, and informative.</p>

<p>In <em>The Divine Comedy</em> by Dante Alighieri, in part <em>Purgatorio</em>, Canto XV (15), Virgil explains to Dante:</p>

<blockquote>
  <p><em>That the more of any material thing one man has, the less of it there is for others;
whereas <strong>the more peace or knowledge or love one man has, the more there is for all the others</strong>.</em></p>
</blockquote>

<p>Happy editing!</p>]]></content><author><name>Finn</name></author><category term="Technology" /><summary type="html"><![CDATA[How to Contribute to Wikipedia — My Experience]]></summary></entry></feed>