Character Copy Kata ( Test Driven Development )
Tags: tdd, typescript, node, webdev
Published: 2023-10-19 08:15:39

A great kata for introducing jest mocks functions in your project.

---

<u>**[Try attempting to solve the kata yourself before looking at the solution : Click here to view the Kata](https://www.tddbuddy.com/katas/character-copy.html)**</u>

---

>## **This Kata expects you to create a character copier class that reads characters from a source and copies them to a destination. The class must copy and write one character at a time**
>
>### _To do this, you need to create a Copier class that takes in a Source  and  a Destination_

<br/>

**The <u>Source interface</u> has a method 
`char ReadChar()`  and the <u>Destination interface</u> has a method  `void WriteChar(char c)`. The Copier class has a method `void Copy()` that reads from the ISource one character at a time and writes to the IDestination until a newline `(\n)` is encountered.**

<br/>

<img src="https://res.cloudinary.com/dde6rnocj/image/upload/v1692347426/char_copy_dia_ba36c84dbd.png" />

<br/>
>@ The copying and writing is done 1 character at a time. 
>
> @ Only the Copier class may exist as a concrete class. 
>
> @ You can use any language  to implement the Source and Destination interfaces


---

> We're going to solve the following problem using _`Typescript`_   and the _`Jest`_  testing framework

<br/>

### First, lets identify all the possible tests you could think of for this scenario 

> [ ❓ ] No characters , ending in newline
> 
> [ ❓ ] Once character , ending in newline
> 
>[ ❓ ] Two characters , ending in newline
>
> [ ❓ ] Many characters, ending in newline
> 
> [ ❓ ] Order of characters copied should be maintained
> 
> [ ❓ ] Characters after newline should not be written


---

_Case 1 :  [❓] No characters , ending in newline_

>Step 1 : <u>Define the most basic test case scenario</u>


```typescript

//copy.test.ts

  describe('when no character is read', () => {
    
    
    it('does not call the desination interface`s writeChar method ', () => {
      const src: Source = getSource([]); // helper method

      const dest = getDestination(); // helper method

      const sut = new Copier(src, dest);

      sut.copy();

      expect(dest.writeChar).toBeCalledTimes(0);
    });
  });
  
```
>Step 2 : <u>Defining the helper functions</u>

>To write tests, we will need two helper methods called <u>getSource </u>and <u>getDestinatio</u>n
 that will provide us with the **<u>Source</u> and <u>Destination</u> interfaces**. 
 >
 >This will allow us to avoid repeatedly creating them for each test, and also make it easier to change any implementation details if needed
 >
 >( p.s : This step can come at a later stage too, after the tests become repetative. 
 >But in our case, we'll be defining them from the very beginning  )
 

```typescript

//copier.helper.ts

import { Source } from './character-copier';

/* The code is creating a mock function `mockCharReader` using `jest.fn()`. This mock function will be
used to simulate the behavior of a character reader object. */

export const getSource = (elements: string[]): Source => {

  const mockCharReader = jest.fn();

  elements.forEach((e) => mockCharReader.mockReturnValueOnce(e));

  /* The line `mockCharReader.mockReturnValue('\n');` is setting the return value of the `mockCharReader`
function to be the newline character (`'\n'`). This means that when `readChar` is called on the mock
character reader object, it will return `'\n'` as the next character from the source. */

  mockCharReader.mockReturnValue('\n');
  
  /* The `return` statement is creating an object with a property `readChar` that is assigned the value
  of the `mockCharReader` function. This object is then returned by the `getSource` function. */
  
  return {
    readChar: mockCharReader,
  };
};

```
---

```typescript

//copier.helper.ts

/* The code is defining a function named `getDestination` that returns an object with two properties:
`writeChar` and `getWrittenChars`. */

export const getDestination = () => {
  const copiedChars: string[] = [];

  return {
  
   /* The `writeChar` property is a function that is defined using `jest.fn()`. This creates a mock
   function that can be used to simulate the behavior of a `writeChar` function. */
   
    writeChar: jest.fn((c: string) => {
      copiedChars.push(c);
    }),
    
/* The `getWrittenChars` property is a function that returns the `copiedChars` array.
This function can be used to retrieve the characters that have been written to the destination. */

    getWrittenChars: () => copiedChars,
    
  };
};

```

---

## Now let's look at the code for our concrete <u>Copy</u> Class

```typescript

\\Copy.ts

export class Copier {
  constructor(
    private readonly src: Source,
    private readonly dest: Destination
  ) {}

  /* The `copy()` method in the `Copier` class is responsible for copying characters from the source
  (`src`) to the destination (`dest`). */
  copy() {
    let char = this.src.readChar();

    while (char !== '\n') {
      this.dest.writeChar(char);

      char = this.src.readChar();
    }
  }
}

export interface Source {
  readChar(): string;
}

export interface Destination {
  writeChar(c: string): void;
}


```

---

>✅ Result :  **A passing test**  🎉

>![A passing test  ](https://res.cloudinary.com/dde6rnocj/image/upload/v1692369832/large_Screenshot_2023_08_18_at_8_12_26_PM_432a69168b.png)

---

## In a similar format , we impelement all the rest of the test cases too 

```typescript

//[+] Once character , ending in newline

  describe('when 1 character is read', () => {
    it.each([{ char: 'a' }, { char: 'b' }, { char: 'c' }, { char: 'd' }])(
      'reads first char from source',
      ({ char }) => {
        const src: Source = getSource([char]);

        const dest = getDestination();

        const sut = new Copier(src, dest);

        sut.copy();

        expect(dest.writeChar).toHaveBeenCalledWith(char);
      }
    );
  });

  //[+] Two characters , ending in newline
  describe('Two characters , ending in newline', () => {
    it.each([
      { chars: ['a', 'b'] },
      { chars: ['c', 'd'] },
      { chars: ['e', 'f'] },
    ])('reads exactly 2 times before encountering a newline', ({ chars }) => {
      const src: Source = getSource(chars);

      const dest = getDestination();

      const sut = new Copier(src, dest);

      sut.copy();

      expect(dest.writeChar).toHaveBeenCalledWith(chars[0]);
      expect(dest.writeChar).toHaveBeenCalledWith(chars[1]);
      expect(dest.writeChar).toBeCalledTimes(2);
    });
  });

  //[+] Many characters, ending in newline
  describe('Many characters, ending in newline', () => {
    it.each([
      { chars: ['a', 'b', 'c', 'd', 'g', 'c'] },
      { chars: ['c', 'd', 'e', 'f', 'd', 'f', 'd'] },
      { chars: ['e', 'f', 'g', 'd', 'd', ''] },
    ])(
      'reads exactly $chars.length times before encountering a newline',
      ({ chars }) => {
        const src: Source = getSource(chars);

        const dest = getDestination();

        const sut = new Copier(src, dest);

        sut.copy();

        expect(dest.writeChar).toBeCalledTimes(chars.length);

        //multiple characters written (order does not matter)
        chars.forEach((c) => expect(dest.writeChar).toHaveBeenCalledWith(c));

        // confirm last called character
        expect(dest.writeChar).toHaveBeenLastCalledWith(
          chars[chars.length - 1]
        );
      }
    );
  });

  //[+] Order of characters
  describe('Order of characters', () => {
    it.each([
      { chars: ['a', 'b', 'c', 'd', 'g', 'c'] },
      { chars: ['c', 'd', 'e', 'f', 'd', 'f', 'd'] },
      { chars: ['e', 'f', 'g', 'd', 'd', ''] },
    ])('has all characters copied in the same order', ({ chars }) => {
      const src: Source = getSource(chars);

      const dest = getDestination();

      const sut = new Copier(src, dest);

      sut.copy();

      expect(dest.writeChar).toBeCalledTimes(chars.length);

      expect(dest.getWrittenChars()).toEqual(chars);
    });
  });

  //[+] Characters after newline should not be written
  describe('Characters after newline should not be written', () => {
    it.each([
      {
        chars: [`1`, `2`, `3`, `4`, `5`, `6`, `7`, `\n`, `a`, `b`],
        before: [`1`, `2`, `3`, `4`, `5`, `6`, `7`],
        after: [`a`, `b`],
      },
      {
        chars: [`1`, `2`, `7`, `5`, `4`, `\n`, `c`, `d`],
        before: [`1`, `2`, `7`, `5`, `4`],
        after: [`c`, `d`],
      },
    ])(
      'has all characters before : $before , the newline  and none after : $after',
      ({ chars, before, after }) => {
        const src: Source = getSource(chars);

        const dest = getDestination();

        const sut = new Copier(src, dest);

        sut.copy();

        expect(dest.getWrittenChars()).toStrictEqual(before);
        expect(!isIntersection(after, dest.getWrittenChars())).toBe(true);
      }
    );
  });

```

> ✅ Result : We pass all the tests 🔥🔥

>![Screenshot 2023-08-18 at 7.29.27 PM.png](https://res.cloudinary.com/dde6rnocj/image/upload/v1692367254/Screenshot_2023_08_18_at_7_29_27_PM_738c68d077.png)

<br/>

**We do need one more helper function <u>`isIntersection`</u> , that lets us find out if any element in one array is present in another array ( i.e  Is there an intersection between the values of 2 arrays ?  )**

```typescript

//copy.helper.ts

 /* The `isIntersection` function takes in two arrays `a` and `b` as parameters. It checks
if there is any element in array `a` that is also present in array `b`. It does this by
using the `some` method on array `a`, which returns `true` if at least one element
satisfies the provided condition. The condition being checked is whether `b` includes
the current element `e` from array `a`. If there is at least one element that satisfies
this condition, the function returns `true`, indicating that there is an intersection
between the two arrays. Otherwise, it returns `false`, indicating that there is no
intersection. */

export const isIntersection = (a: string[], b: string[]) =>a.some((e) => b.includes(e));

```

## 👍  Great !!  This concludes our  tutorial .

### If you want ,  you can have a look at the souce code on GitHub  : _<u>[Click Here To View Source Code](https://github.com/code0monkey1/character-copier-tdd-typescript)</u>_

>If you have any questions regarding the exercise , you're free to add a comment on the blog post and i'll try to answer it ASAP 🧑‍💻 !