me
Published on

Testing with chatgpt

Authors
  • avatar
    Name
    Omer Atagun
    Twitter

You have heard a lot about how chatgpt boosts productivity, yes it does and takes away the boredom of repetitive but necessary tasks. Testing is a major aspect in software engineering. Helps your pairs/other engineers to understand the specifications and capabilities of the solution provided by you. It helps speed up adjustment process when a requirements also change. Long story short, this is about how i have leveraged chatgpt to write unit tests in a project that did not have any. Even though i had no clue what the project was about.

Understanding the code

At some point in your career, you learn empathy is the key to understand somebody elses solution. Yet, there are days you may need some help or you may need to ask some questions about the original solution. Well, no more. Chatgpt can help you out with the intention. May get the entire picture wrong but remember, it is all about predicting not certainity. As like empathy.

Lets go through some examples of how can we leverage chatgpt doing this task. Providing example below;

export const hidePrivateProperties = <T, N>(
  object: T,
  properties: Array<N extends keyof T ? N : any>
): Omit<T, N extends keyof T ? N : any> => {
  properties.forEach((property) => {
    delete object[property]
  })

  return object
}

Some utility method that helps deleting properties of a given object and returns the same and altered(no copy) object with new type type that omits those properties from the given object.

Lets ask chatgpt what this does.

  • Q: Can you tell me what this exactly does?
In summary, this function takes an object and an array of properties,
and it removes those properties from the object.
The types are designed to ensure that the properties
being hidden are actually properties of the object.
Note that using delete on an object modifies it in place, and
the modified object is then returned.

FYI i kept entire description hidden and copied only the summary. So now we know that this function does not copy the object, it alters and returns the original object.

Lets ask chatGPT to write unit tests for this utility function and do some mocks.

  • Q: Can you write me unit tests in jest, also provide some mocks. I have jest environment set already, so skip that part.
// Import the function
import { hidePrivateProperties } from './your-file'

// Mock data for testing
interface TestData {
  name: string
  age: number
  address: string
}

const mockObject: TestData = {
  name: 'John Doe',
  age: 25,
  address: '123 Main St',
}

describe('hidePrivateProperties', () => {
  it('should hide specified properties from the object', () => {
    // Arrange
    const propertiesToHide = ['age', 'address']

    // Act
    const result = hidePrivateProperties(mockObject, propertiesToHide)

    // Assert
    expect(result).toEqual({ name: 'John Doe' })
  })

  it('should handle an empty array of properties', () => {
    // Arrange
    const propertiesToHide: string[] = []

    // Act
    const result = hidePrivateProperties(mockObject, propertiesToHide)

    // Assert
    expect(result).toEqual(mockObject) // The object should remain unchanged
  })

  it('should handle properties that do not exist on the object', () => {
    // Arrange
    const propertiesToHide = ['nonexistentProperty']

    // Act
    const result = hidePrivateProperties(mockObject, propertiesToHide)

    // Assert
    expect(result).toEqual(mockObject) // The object should remain unchanged
  })

  it('should handle an empty object', () => {
    // Arrange
    const emptyObject: TestData = {}

    // Act
    const result = hidePrivateProperties(emptyObject, ['property1', 'property2'])

    // Assert
    expect(result).toEqual({}) // The object should remain empty
  })

  // You can add more test cases based on your specific use cases
})

Pretty good right? chatgpt understood the probable scenarios, provided tests for it and let you implement. So, i heard you are saying that you could also do this or even have done it before, why this blog post now?

Well, we are not done yet. Nice thing about bots that they may see better than you most of times with given enough of context.

Q: Do you think we may have an edge case in this utility function? if so, could you write me a tests for that?

describe('hidePrivateProperties', () => {
  // ... other test cases ...

  it('should handle a null object', () => {
    // Arrange
    const nullObject: TestData | null = null
    const propertiesToHide = ['age', 'address']

    // Act
    const result = hidePrivateProperties(nullObject!, propertiesToHide)

    // Assert
    expect(result).toBeNull() // The result should also be null
  })

  it('should handle an undefined object', () => {
    // Arrange
    const undefinedObject: TestData | undefined = undefined
    const propertiesToHide = ['age', 'address']

    // Act
    const result = hidePrivateProperties(undefinedObject!, propertiesToHide)

    // Assert
    expect(result).toBeUndefined() // The result should also be undefined
  })
})

Now when we put all these into test, we can easily see that we have a flaw in our utility function;

TypeError: Cannot convert undefined or null to object

      4 |    ): Omit<T, N extends keyof T ? N : any> => {
      5 |     properties.forEach((property) => {
    > 6 |       delete object[property];
        |                     ^
      7 |     });
      8 |
      9 |     return object;

Same problem with null as well. You see, our generic T was too generic :). It indeed did allow undefined or null as an argument when it should only be an object.

Thanks to the bot itself, we figured that we have a flaw in our function and might tickle us later in time. If we would refactor this generic with expectation of a something looks like and only like object, we would not even need these tests that measures undefined and null scenarios.

export const hidePrivateProperties = <T, N>(
  object: T extends Record<string, any> ? T : never, // if it aint object, then never to me :)
  properties: Array<N extends keyof T ? N : any>
): Omit<T, N extends keyof T ? N : any> => {
  properties.forEach((property) => {
    delete object[property]
  })

  return object
}

Guess what, this was not enough either :) because if we pass an array of objects, typescript will not warn us and this method will behave totally irrelevant. Because it will do nothing but return the same array of objects given.

it('should handle an array of objects', () => {
    const mockResult = {
        surname: 'sdsds'
    }
    // Arrange
    const result = hidePrivateProperties([{
        name: 'asd',
        surname: 'sdsds'
    }], ['name'])

    // Assert
    expect(result).toEqual(mockResult); // The result should also be undefined
  });

  ● hidePrivateProperties › should handle an array of objects

    expect(received).toEqual(expected) // deep equality

    Expected: {"surname": "sdsds"}
    Received: [{"name": "asd", "surname": "sdsds"}]

      63 |
      64 |     // Assert
    > 65 |     expect(result).toEqual(mockResult); // The result should also be undefined
         |                    ^
      66 |   });
      67 |   // You can add more test cases based on your specific use cases
      68 | });

Q: with above, typescript is still okay if i pass array of objects, we need to block that

export const hidePrivateProperties = <T, N>(
  // focus here, chatgpt conditioned for array of objects but did not think of others again.
  object: T extends Record<string, any>[] ? never : T,
  properties: Array<N extends keyof T ? N : any>
): Omit<T, N extends keyof T ? N : any> => {
  properties.forEach((property) => {
    delete object[property]
  })

  return object
}

Above would be resulted back again that our generic accepts every primitive values including undefined and null. So if we would be just copy pasting, we would be wrong.

Don't know about you but to me bot had helped discovering a lot of problems in nice looking generic at first sight. Now i can cover the requirements and be done with it.

type TOnlyObject = Record<string, any>[] | null | undefined | boolean | string | number | symbol
object: T
extends TOnlyObject ?
never : T,

Conclusion

ChatGPT or any other AI will help you and boost your productivity but that does not mean will do %100 of the job for you. Leveraging bot will speed up this process and help you discover your flaws and make you better in terms of shipping any solution. Hence, you will have your pair programmer, reviewer with you at all times :)

Note: if this type TOnlyObject comes a bit bloated to your eyes, ask chatGPT to simplify ;) also those any should be never to avoid even more tests :)

Until next time, stay hydrated!