0

I am stuck with Jest testing of file resize/upload logic. This is a simple job to resize uploaded image and add it to the Notes & Attachments based on recordId. It is not a production code - it is created exacly for testing purposes Works fine in developer org. Heroku just receives Base64 string - resizes it - and sends back (no backend code is provided here). So based on Component's js file I have to test:

  1. onchange={openFileUpload} in lightning-input type="file"
  2. new FileReader()
  3. Imperative method call (ImageUpload.uploadFile )
  4. Notification - toast(title) - which is triggered from then block of the Promise

And all in one file in some sort of chain I guess.

Keeping in mind that I am not a Jest ninja, this task is too much for me. I've done some tests but I am not sure if they are correct. What worries me the most is that the toast is not automatically fired in 'then' block of 'uploadFile' imperative method. I can call it manually but it doesn't make sense. Please take a look.

<template>
    <lightning-card  title="File Upload LWC" icon-name="action:upload" >
        <div>
            <lightning-input type="file" 
            label="Attachment"
            accept=".jpg, .png, .gif"
            onchange={openFileUpload}
            ></lightning-input>
        </div>
    </lightning-card>
</template>


imageUpload.js
==
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import uploadFile from '@salesforce/apex/ImageUpload.uploadFile'

export default class ImageUpload extends LightningElement {
  openFileUpload(event) {
    if (event?.target.files?.length) {
      const recordId = '0010900000c7vKKAAY'
      const file = event.target.files[0]
      const filename = file.name
      // Mozila FileReader
      // The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, 
      // using File or Blob objects to specify the file or data to read.
      const reader = new FileReader() //The readAsDataURL method is used to read the contents of the specified Blob or File
      reader.readAsDataURL(file) // Syntax - readAsDataURL(blob)
      reader.onload = () => {
        var base64 = reader.result.split(',')[1]

        uploadFile({ base: base64, name: filename, id: recordId }).then(result => {
          if (result === 'ok') {
            let title = `${filename} uploaded successfully!`
            this.toast(title)
          } else {
            let title = result
            this.toast(title)
          }
        })
          .catch((error) => {
            console.log(error)
            let title = 'Error'
            this.toast(title)
          })
      }
    }
  }

  toast(title) {
    const toastEvent = new ShowToastEvent({
      title,
      variant: "success"
    })
    this.dispatchEvent(toastEvent)
  }
}


ImageUpload.cls
==
public with sharing class ImageUpload {

    @AuraEnabled
    public static String uploadFile (String base, String name, String id) {
        String content = '{"str": "' + base + '"}';
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(  'https://some-address.herokuapp.com/image'  );
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(content);
        HttpResponse response = h.send(req);

        String jsonRes = response.getBody();
        Map<String,Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(jsonRes);
        String base64 = (String)responseMap.get('str');
        if (base64 == 'Error') {
            return  base64;
        } else {
           ContentVersion cv = createContentVersion( base64, name);
           ContentDocumentLink cdl = createContentLink( cv.Id,  id);
           if (cv == null || cdl == null) {
               return 'Error';
           }
           return 'ok';
        }
    } // uploadFile


    private static ContentVersion createContentVersion(String base64, String filename) {
        ContentVersion cv = new ContentVersion();
        //EncodingUtil class - https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_classes_restful_encodingUtil.htm
        cv.VersionData = EncodingUtil.base64Decode(base64); 
        cv.Title = filename;
        cv.PathOnClient = filename;
        try {
          insert cv;
          return cv;
        } catch(DMLException e) {
          System.debug(e);
          return null;
        }
      }
    
      private static ContentDocumentLink createContentLink(String contentVersionId, String recordId) {
       if (contentVersionId == null || recordId == null) {
            return null;
         }
        ContentDocumentLink cdl = new ContentDocumentLink();
        cdl.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id =: contentVersionId ].ContentDocumentId;
        cdl.LinkedEntityId = recordId;
        cdl.ShareType = 'V';
        try {
          insert cdl;
          return cdl;
        } catch(DMLException e) {
          System.debug(e);
          return null;
        }
      }
}


imageUpload.test.js
==
import ImageUpload from 'c/ImageUpload'; 
import uploadFile from '@salesforce/apex/ImageUpload.uploadFile'

jest.mock(
    '@salesforce/apex/ImageUpload.uploadFile',
    () => {
        return {
            default: jest.fn()
        };
    },
    { virtual: true }
);

const mockToast = jest.fn();
jest.mock('c/ImageUpload', () => {
    return jest.fn().mockImplementation(() => {
        return { toast: mockToast };
    });
});

describe('c-image-uploaded', () => {

    beforeEach(() => {
        ImageUpload.mockClear();
        mockToast.mockClear();
    });

    afterEach(() => {
        // restore the spy created with spyOn
        jest.restoreAllMocks();
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
    });
    async function flushPromises() {
        return Promise.resolve();
    }

    it('FileReader receives Blob', async () => {
        let blob = new Blob([""], { type: 'image/png' })
        blob.lastModifiedDate = ""
        blob.name = "coolImage"
        const fileReader = {
            readAsDataURL: jest.fn(),
        }
        jest.spyOn(window, 'FileReader').mockImplementation(() => fileReader)
        fileReader.readAsDataURL(blob)
        await flushPromises()

        expect(fileReader.readAsDataURL).toBeCalledWith(blob);
    })

    it('Call uploadFile with params', async () => {
        const base64 = 'BWpmdYyVL1XcQGZ86EPuuw==' //I used  https://generate.plus/en/base64
        const recordId = '0010900000c7vKKAAY'
        const filename = 'coolImage'
        uploadFile({ base: base64, name: filename, id: recordId })
        await flushPromises()

        expect(uploadFile).toBeCalledWith({ base: base64, name: filename, id: recordId })
        expect(uploadFile).toHaveBeenCalledTimes(1)
    })

    it('kinda toast testing', async () => {
        const filename = 'cool image'
        uploadFile.mockResolvedValue('ok')
        const data = await uploadFile()
        let title
        if (data === 'ok') {
            title = `${filename} uploaded successfully!`
        } else {
            title = 'some title'
        }
        mockToast(title)
        await flushPromises()

        expect(mockToast).toBeCalledWith(`${filename} uploaded successfully!`)
        expect(mockToast).toHaveBeenCalledTimes(1)
    })

    it('resolved promise', async () => {
        uploadFile.mockResolvedValue('ok')
        const data = await uploadFile()

        expect(data).toBe('ok')
        // or
        await expect(uploadFile()).resolves.toBe('ok')  // third call
    })

    it('rejected promise', async () => {
        uploadFile.mockRejectedValue(new Error('Async error message'))

        await expect(uploadFile())
            .rejects
            .toThrow('Async error message');
    })
}) // describe

0

You must log in to answer this question.

Browse other questions tagged .