Writing User Info to files in CSV format ( TDD Kata ) - Part 1
Tags: webdev, tdd, typescript, node
Published: 2023-10-19 08:10:37

How does one handle changes to the requirements in a real-life setting while building software using Test Driven Development?

This article explores  different techniques one can apply in situations where requirements for a feature keep changing constantly


---

# **_Summary :_**

How does one handle <u>changes in feature requirements</u> while building software using Test Driven Development ?    

This article explores different techniques one can apply in situations where requirements for a feature keep changing constantly .
<br/>
We will require the knowledge of <u>S.O.L.I.D principles</u> , <u>Typescript </u> and some knowledge of <u>creating and using  mocks in Jest</u> ( However , you're free to use whichever language and testing framework you like )   
<br/>

There will be <u>4 different parts to this exercise</u> , reflecting the different requirements that were brought in by the product manager at different stages of the prouduct development process


>Task 1 : Write Customer information to a file  in CSV format 
>
>Task 2 : Write Customer information in batches of N customers at a time 
>
>Task 3 : Wrire Customer information , with no duplicate entries
>
>Task 4 : Write Customer information to 2 different sources ( one with duplicates and one without for monitoring purposes )


## <u>In this specific post , we'll be concerntating on the 1st task and getting things set up !</u>


### First Let's look at the UML Diagram for the particular situation : 

![Screenshot 2023-08-24 at 5.08.47 PM.png](https://res.cloudinary.com/dde6rnocj/image/upload/v1692877213/Screenshot_2023_08_24_at_5_08_47_PM_f7a9c489d6.png)


---
<br/>
**<u>Code</u> :**

```
//Customer.ts

class Customer {
  constructor(private _name: string, private _contactNumber: string) {}
  constructor(private _name: string, private _contactNumber: string) {}

  public get name(): string {
    return this._name;
  }

  public set name(value: string) {
    this._name = value;
  }

  public get contactNumber(): string {
    return this._contactNumber;
  }

  public set contactNumber(value: string) {
    this._contactNumber = value;
  }


}

export default Customer;

```

---

```
\\FileWriter.ts

interface FileWriter {
  writeLine(fileName: string, line: string): void;
}

export default FileWriter;

```
---

```
//CustomerFileWriter.ts

export class CustomerFileWriter  {
  constructor(private readonly fileWriter: FileWriter) {}

  writeCustomers(fileName: string, customers: Customer[]) {
   // to implement !!! 
  }

  private customerToString = (customer: Customer) => {
    return `${customer.name},${customer.contactNumber}`;
  };
}

```

---

<br/>

( We'll be exploring a  slightly <u>different flavor</u> of Test Driven Development called<u> Behavior Driven Development</u> )    
<br/>

##### What  separates this methodology from traditional T.D.D is the focus on getting a feature to work, and s<u>ingle-mindedly </u>writing tests to achieve the same.

>Personally, I aim to get the _happy paths_ for a given feature passing, as soon as possible !

<br/>

**The are 3 things you should always keep in mind while formulating a B.D.D test** 

>1. `GIVEN ( the starting condition )`
>1. `WHEN ( some action occurs )`
>1. `THEN ( expected result achieved )`

<hr/>


## To further discuss the test cases for the behavior we expect when attempting to write a customer's information to a file, we can consider the following scenarios:


<hr/>

_Case 1_ :    
   > given : **there is no customer object in the customers array**
   >
  > when : **we try to write customers to a file**
  >
 >  then : **nothing is written to a file ( i.e <u>writeLine</u> function is not called)**
  
 
```
\\customer-file-writer.test.ts


 it(' will not write to file , when there is no customer', () => {
 
    //arrange
    const fileWriter = getFileWriter();

    const fileName = 'myfile.pdf';

    //act
    const sut = getCustomerFileWriter(fileWriter);

    sut.writeCustomers(fileName, []);

    //assert
    expect(fileWriter.writeLine).toBeCalledTimes(0);
    
  });
  ```
  ---
  
_Helper functions :_

  ```
  \\customer-file-writer.helper.ts
  
  export const getFileWriter = (): FileWriter => {
  return {
    writeLine: jest.fn((_fileName: string, _line: string) => {}),
  };
};

export const getCustomerFileWriter = (
  fileWriter: FileWriter
): ICustomerFileWriter => {
  return new CustomerFileWriter(fileWriter);
};

```
  
  > Result : ✅ Passing Test 
  
![Screenshot 2023-08-24 at 6.14.32 PM.png](https://res.cloudinary.com/dde6rnocj/image/upload/v1692881144/Screenshot_2023_08_24_at_6_14_32_PM_bc1ea91e6f.png)
  
  ---
  
> 🧐 <u>Let's reflect on what we just did</u> : 
> <br/>
> We are able to pass this test without even implementing any logic ! Great !
>
> So now what would happen when we have <u>1 customer is the customer array </u>? 
> <br/>
> What do we expect in that case ? 


---

_Case 2_ : 

  > given : **single customer object is present**
  >
  > when : **we try to write the object info to file**
  >
  > then : **only single object with the given info gets written**


 
```
\\customer-file-writer.test.ts

 it('single customer object is present', () => {
    //arrange
    const fileWriter = getFileWriter();
    const customer = getCustomer('Glen', '32');
    const fileName = 'myfile.pdf';

    //act
    const sut = getCustomerFileWriter(fileWriter);
    sut.writeCustomers(fileName, [customer]);

    //assert

    expect(fileWriter.writeLine).toBeCalledTimes(1);
    expect(fileWriter.writeLine).toHaveBeenLastCalledWith(
      fileName,
      customerToString(customer)
    );
  });
```

---

Helper function :

```
//customer-file-writer.helper.ts

export const getCustomer = (name: string, contactNumber: string): Customer => {
  return new Customer(name, contactNumber);
};

export const customerToString = (customer: Customer) => {
  return `${customer.name},${customer.contactNumber}`;
};

```

 > ❌  Now if you try to run the test , you'll encounter a test failure . 
>
> To mitigate that you'll have to write the least possible code in <u>CustomerFileWriter</u> to get the test passing


``` 
Before Modification :  customer-file-writer.ts

//customer-file-writer.ts

export class CustomerFileWriter  {
  constructor(private readonly fileWriter: FileWriter) {}

  writeCustomers(fileName: string, customers: Customer[]) {
 // to implement !!! 
  }

  private customerToString = (customer: Customer) => {
    return `${customer.name},${customer.contactNumber}`;
  };
}

```

```
\\ Modified :  customer-file-writer.ts

export class CustomerFileWriter implements ICustomerFileWriter {
  constructor(private readonly fileWriter: FileWriter) {}

  writeCustomers(fileName: string, customers: Customer[]) {
  
   // this is the single implementation change we make
   
 this.fileWriter.writeLine(fileName, this.customerToString(customers[0]));
 
   //
   
  }

  private customerToString = (customer: Customer) => {
    return `${customer.name},${customer.contactNumber}`;
  };
}
```
> Result : ✅ Passing test ! 
![Screenshot 2023-08-24 at 6.40.37 PM.png](https://res.cloudinary.com/dde6rnocj/image/upload/v1692883797/Screenshot_2023_08_24_at_6_40_37_PM_935aba2c84.png)

---

> 🧐  <u>Let's reflect on what we did</u> : 
> <br/>
> We changed a single line , and wrote a single customer's information to a file .
> <br/>
> Will this work for <u>multiple customers</u> ? 

---

_Case 3_ :

> given : **multiple customer objects are present**
> 
> when : **we try to write the objects info to file**
> 
> then : **the exact number and the exact order of customers are written to file**


```
\\ customer-file-writer.test.ts

 it.each([
    {
      customers: createCustomers(3),
    },
    {
      customers: createCustomers(12),
    },
  ])(
    '$customers.length customers info gets written , in the proper order ',
    ({ customers }) => {
   
   //arrange
      const fileWriter = getFileWriter();
      const fileName = 'narnia.ts';

     
     //act
      const sut = getCustomerFileWriter(fileWriter);

      sut.writeCustomers(fileName, customers);

      //assert
      assertCustomersHaveBeenWritten(fileWriter, fileName, customers);
  
  //has same number of items as the number of customers

      expect(fileWriter.writeLine).toHaveBeenCalledTimes(customers.length);

// items written in same order , i.e last item written last 

      expect(fileWriter.writeLine).toHaveBeenLastCalledWith(
        fileName,
        `${customers[customers.length - 1].name},${
          customers[customers.length - 1].contactNumber
        }`
      );
    }
 
  
  ```
  Helper Function : 
  
  ```
  //customer-file-writer.helper.ts

  export const createCustomers = (count: number) => {
  const customers: Customer[] = [];

  for (let i = 1; i <= count; i++) {
    customers.push(getCustomer(i + '', i + ''));
  }

  return customers;
};
  
  
  ```
  
  > ❌ This again results in a test failure 
  > <br/>
 >  Now, let's again think about the least amount of code we can write to make the tests pass .



``` 
\\ Modified :  customer-file-writer.ts


export class CustomerFileWriter implements ICustomerFileWriter {
  constructor(private readonly fileWriter: FileWriter) {}

  writeCustomers(fileName: string, customers: Customer[]) {
  
 // we modified this line : this.fileWriter.writeLine(fileName, this.customerToString(customers[0]));
 
 // rather than writing a single customer , this time , we write multiple customers 
 
    customers.forEach((customer) => {
      this.fileWriter.writeLine(fileName, this.customerToString(customer));
      
    });
    
  }  }

```

> Result : ✅ Our tests for multiple customer pass 🔥🔥
> 
![Screenshot 2023-08-24 at 7.42.16 PM.png](https://res.cloudinary.com/dde6rnocj/image/upload/v1692886481/Screenshot_2023_08_24_at_7_42_16_PM_7f7f76cf2b.png)

---

<br/>
<br/>
_That's all for now !_
<br/>
<br/>
**Stay tuned for when our <u>product manager</u> arrives with a new set of modifications for our Customer File Writer feature .**
<br/>
<br/>
**We'll have to modify and adapt to the new  requrements , and ensure we do the least amount of modification to our code and keep our implementation SOLID compliant .**

<br/>
---

<u>[Click Here : GitHub Code Repo ](https://github.com/code0monkey1/csv-file-kata)</u>

---