The Spy of Testadel: A Medieval Testing Tale
Tags: dependencyinjection, dependencyinversionprinciple, testing, spies
Published: 2021-02-23 21:33:45

---
title: The Spy of Testadel: A Medieval Testing Tale
published: true
date: 2019-12-26 16:55:57 UTC
tags: DependencyInjection,DependencyInversionPrinciple,Testing,Spies
canonical_url: https://blog.snowfrog.dev/spy-of-testadel/
---

 ![The Spy of Testadel: A Medieval Testing Tale](https://images.unsplash.com/photo-1514539079130-25950c84af65?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)

The year is 1453. After 116 years of war, your kingdom, Testadel, is finally at peace. Now is the time to rebuild and strengthen your relationships with your neighbours. To the North lies the fiefdom of Lord PureFunction, who fought valiantly at your side during the war. In an effort to establish stronger commercial ties, you both sign a treaty whereby you promise to supply his keep with as many sheep as you can every month, in exchange for 15 pieces of gold per sheep.

<!--kg-card-begin: markdown-->
```ts
class LordPureFunctionsKeep {
  priceOfOneSheep = 15;

  calculatePaymentForSheep(sheep: number): number {    
    return sheep.length * this.priceOfOneSheep;
  }
}
```
<!--kg-card-end: markdown-->

After a few months, as was agreed by both parties, an independent auditor travels to Lord PureFunction's keep to make sure that the commercial agreement is being fulfilled properly.

<!--kg-card-begin: markdown-->
```ts
describe('LordPureFunctionsKeep'() => {
  test('should pay 300 gold pieces when supplied with 20 sheep', () => {
    const keep = new LordPureFunctionsKeep();

    const payment = keep.calculatePaymentForSheep(20);

    expect(payment).toBe(300);
  });
});
```
<!--kg-card-end: markdown-->

Upon completion, the auditor reports that Lord PureFunction was very accommodating. A true paragon of virtue, PureFunction opened up its books willingly and complied happily with every request from the auditor. "Usually", the auditor says, "people try to hide things from me, but not so with Lord PureFunction. You are lucky to have such a transparent and trustworthy ally".  
  
Happy with the auditor's findings and secure in the knowledge that you have a strong relationship with your ally to the North, you turn your attention to the South. There lie the ravaged lands of Sir SideEffects.

![The Spy of Testadel: A Medieval Testing Tale](https://images.unsplash.com/photo-1539871283820-aec0272ade46?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@udit_arun?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">udit saptarshi</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

Sir SideEffects had the misfortune of taking the other side, the losing side, during the recently ended war. The peace treaty that ended the war, and that he signed reluctantly, imposes serious limitations on the amount of weapons he is allowed to stockpile and forbids him from having any contacts with his previous allies.  
  
It is agreed that Sir SideEffects will provide every month to your envoy a full account of his weapons stock.

<!--kg-card-begin: markdown-->
```ts
class SirSideEffectsCamp {
  private armoury: Weapons[] = [] ;
  private scribe = new Scribe();
	
  reportWeaponsStock(): number {
    const report = this.scribe.writeWeaponsReport(this.armoury);
    return report;
  }
}
```
<!--kg-card-end: markdown--><!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should report having less than 50 weapons', () => {
    const camp = new SirSideEffectsCamp();

    const weaponsStock = camp.reportWeaponsStock();

    expect(weaponsStock).toBeLessThan(50);
  });
});
```
<!--kg-card-end: markdown-->

For the first few months, Sir SideEffects seems to abide by the terms of his surrender. Every month your envoy shows up at the gate, asks for a report, and every month Sir SideEffects has a piece of paper attached to a rock thrown over the rampart, detailing the level of his weapons stock. The amount varies a little every month but is always below the agreed ceiling.

![The Spy of Testadel: A Medieval Testing Tale](https://blog.snowfrog.dev/content/images/2019/12/pablo--7-.png)

Everything seems under control until one day your envoy asks to speak with you about a matter that seems to have him worried. "My lord, I fear that Sir SideEffects might be up to no good. Indeed, this is the fourth month in a row that he has reported exactly 6 weapons. Something's not right; the amount always used to vary before".

<!--kg-card-begin: markdown-->
```ts
class SirSideEffectsCamp {
  private armoury = Weapons[] = [];
  private scribe = new Scribe();
	
  reportWeaponsStock(): number { 
    const report = this.scribe.writeWeaponsReport(this.armoury);
    return 6; // <- Now reports 6 weapons
  }
}
```
<!--kg-card-end: markdown-->

Taking this matter to heart, you convene a meeting of your councillors to address the situation with Sir SideEffects. After debating for the better part of the day, everyone agrees that although it is highly suspicious that the reports for the past fours months have all been the same, it is not in itself a proof of foul play. More information needs to be gathered. But how? Sir SideEffects keeps the gate to his camp closed to outsiders and it is impossible to see what is going on inside.

One of your councillors speaks up. "Sir, we need to send a spy. Someone crafty enough to find a way in without being recognized and raising the alarm. The spy could check the weapons store and report back. Then we'll know for sure."

![The Spy of Testadel: A Medieval Testing Tale](https://blog.snowfrog.dev/content/images/2019/12/pablo--8-.png)

And so it is decided, a spy will be sent to Sir SideEffects' camp, with the difficult mission of infiltrating the camp and gathering vital information. Thankfully, you know just the man for the job. Your court jester, aptly nicknamed Jest, is a master of disguise and despite what some might think, an intelligent and resourceful man.

Later, as you go over the mission's plan with Jest, he points out a problem with the plan. If he is to somehow infiltrate the camp under the guise of one of its inhabitants, an opportunity for a swap must be created. At the moment, such an exchange is impossible as all of the camp's residents remain inside day and night.

"Leave that up to me," you say, "I know just what to do. I'll send forged orders to Sir SideEffects' scribe requiring him to leave the camp for the day. Upon his return, you can intercept him a short distance from the gate and take his place. You should be able to walk right through the front gate if your disguise is convincing enough."

As Jest makes preparation for his departure, you arrange for the forged orders to be sent to Sir SideEffects' scribe.

<!--kg-card-begin: markdown-->
```ts
class SirSideEffectsCamp {
  private armoury = Weapons[];
  constructor(private scribe: Scribe) { } // <- Scribe now passes through gate
	
  reportWeaponsStock(): number { 
    const report = this.scribe.writeWeaponsReport(this.armoury);
    return 6;
  }
}
```
<!--kg-card-end: markdown-->

The next day, exactly as planned, Jest lies in wait by the wayside, about half a league from SideEffects' camp. As the sun begins to set, he finally sights his target. The scribe, back from surveying Sir SideEffects' fields, can already taste the turnip and cabbage soup waiting for him at home. Lost in his thoughts, he pays no attention to his surroundings. As he passes by the bushes where Jest had been lying in wait, he receives a massive blow to the head and immediately falls to the ground, unconscious.

Jest drags the scribe's limp body into the bushes, removes his clothing and adorns himself with the lavish clothes and accoutrement.

<!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should report having less than 50 weapons', () => {
    // Our spy mimicks the scribe's ability to 'writeWeaponsReport'
    // but makes sure to keep a copy of the report in his pocket
    const spyScribe = {
      writeWeaponsReport(armoury: Weapons[]) {
        const report = armoury.length;
        spyScribe.pocket = report; // <- Keep copy of report in pocket
        return report;
      }
    }
    const camp = new SirSideEffectsCamp();

    const weaponsStock = camp.reportWeaponsStock();

    expect(weaponsStock).toBeLessThan(50);
  });
});
```
<!--kg-card-end: markdown-->

All that is left now is to walk through the front gate and take the scribe's place.

<!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should report having less than 50 weapons', () => {
    const spyScribe = {
      writeWeaponsReport(armoury: Weapons[]) {
        const report = this.armoury.length;
        spyScribe.pocket = report;
        return report;
      }
    }
    const camp = new SirSideEffectsCamp(spyScribe); // <- Spy infiltrates the camp

    const weaponsStock = camp.reportWeaponsStock();

    expect(weaponsStock).toBeLessThan(50);
  });
});
```
<!--kg-card-end: markdown-->

The next morning, your envoy shows up at the gate of Sir SideEffects' camp and asks for a report like he did every month since the end of the war. This time though, your spy is in place inside the camp and is able to observe the weapons stock from the inside.

<!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should report having less than 50 weapons', () => {
    const spyScribe = {
      writeWeaponsReport(armoury: Weapons[]) {
        const report = this.armoury.length;
        spyScribe.pocket = report;
        return report;
      }
    }
    const camp = new SirSideEffectsCamp(spyScribe);

    camp.reportWeaponsStock();

    expect(spyScribe.pocket).toBeLessThan(50); // <- Get spy's report
  });
});
```
<!--kg-card-end: markdown-->

Later that day, your spy manages to sneak out and report his findings. Although the report given by Sir SideEffects once again said that he only had 6 weapons in his armory, Jest reports that he in fact has close to 100 weapons in stock and that he has been willfully giving false reports.

What's more, while in the camp, Jest has heard a troubling rumour. Word around the drinking well is that Sir SideEffects has taken to dark magic. Apparently, the rumour goes, Sir SideEffects is in possession of a magical artifact that allows him to communicate directly with anyone, however far removed from the camp they may be. The rumour even goes as far as to say that the artifact - some say it is called _The Console_<sup>TM</sup> - would allow him to communicate with mystical beings from another realm.

![The Spy of Testadel: A Medieval Testing Tale](https://blog.snowfrog.dev/content/images/2019/12/pablo--9-.png)

Though this seems a little far-fetched, you do suspect Sir SideEffects is using the artifact to communicate with his old ally, Sir EvilScreen.

<!--kg-card-begin: markdown-->
```ts
class SirSideEffectsCamp {
  private armoury = Weapons[];
  constructor(private scribe: Scribe) { }
	
  reportWeaponsStock(): number { 
    const report = this.scribe.writeWeaponsReport(this.armoury);
    console.log(`We now have ${report} weapons, death to Testadel!`); // <-
    return 6;
  }
}
```
<!--kg-card-end: markdown-->

If only there was a way to intercept the messages being sent through this magical object. "My Lord," a croaked voice is heard from the back of the hall as a wizened old man steps forward, "my name is Sir Mockalot and through my travels and adventures, I have acquired an object that I believe may be of assistance".

Sir Mockalot explains that he possesses an artifact called the _The Mocksole_<sup>TM</sup>. "It is said to have been forged together with _The Console_<sup>TM</sup> and to possess very similar properties. If we could substitute _The Console_<sup>TM</sup> with our own artifact, we would be the ones receiving Sir SideEffects' messages and he would be none the wiser".

As Sir Mockalot, accompanied by Jest, your trusty jester / spy,  goes to his chamber to prepare _The Mocksole_<sup>TM</sup>  for delivery to Sir SideEffects camp,

<!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should not be sending treasonous messages', () => {
    const theMocksole = { // <- Preparing the Mocksole
      log: jest.fn()
    }
  });
});
```
<!--kg-card-end: markdown-->

you set to writing an accompanying note.

"Dear Sir SideEffects, I was not able to hear your last message through _The Console_<sup>TM</sup> as the sound was all garbled. Please find attached to this note, a replacement artifact to be used in its stead. Sincerely, Lord EvilScreen."

The next day, _The Mocksole_<sup>TM</sup> is found at the camp's gate and brought in. Sir SideEffects reads the note and complies with its message.

<!--kg-card-begin: markdown-->
```ts
class SirSideEffectsCamp {
  private armoury = Weapons[];
  constructor(private scribe: Scribe, private artifact: Console) { } // <- A console-like artifact at the gate
	
  reportWeaponsStock(): void { 
    const report = this.scribe.writeWeaponsReport(this.armoury);
    artifact.log(`We now have ${report} weapons, death to Testadel!`); // <- Let's use it instead of the old console.
    return 6;
  }
}
```
<!--kg-card-end: markdown-->

Everything is now in place to intercept the messages, all you need to do is stand by and listen.

<!--kg-card-begin: markdown-->
```ts
describe('SirSideEffectsCamp'() => {
  test('should not be sending treasonous messages', () => {
    const theMocksole = {
      log: jest.fn()
    }
    const treasonousTalk = 'death to Testadel!'
    const camp = new SirSideEffectsCamp(spyScribe, theMocksole);

    camp.reportWeaponsStock();

    expect(theMocksole.mock.calls[0][0]).not.stringMatching(treasonousTalk);
  });
});
```
<!--kg-card-end: markdown-->

Horror! It is as you feared, Sir SideEffects is planning a rebellion. Thanks to the valiant efforts of your subjects you are made aware of this fact before it is too late and can make preparations. Peace will have unfortunately been short-lived.