#just make Normal queueable
Explore tagged Tumblr posts
Text
honestly, at this rate,
I'm surprised they still have LFR in retail.
lol, no LFR in MoP Classic.
Typical Blizzard L.
#imagine if they did remove it tho#hoooooo boy lol#just make Normal queueable#and make every difficulty have every mechanic just lower the numbers#idk why they even remove mechanics from LFR. that isn't learning ot me#god forbid a player who has never done normal and heroic raiding has to learn in a pug#i did make my own raid group once#Once.#normal mode amirdrassil. first run was great#second run. different day. different group. not so much
1 note
·
View note
Link
This document provides a testing solution for the Apex Continuations implemented in a static-context (commonly used in Aura and Lightning Web Components). All the source code displayed below is also available in this repository Please, note that this is not an official Salesforce solution but just a custom workaround What is an Apex continuation? Apex Continuations are a mechanism provided by the Salesforce platform that allow you to make asynchronously, long-running requests to an external Web Service. The service supports callouts to a SOAP or REST Web Service, with a maximum timeout of 120 seconds (versus 10 seconds in a standard synchronous callout). To give a more visual description of this concept, look at the diagram below: (1) A user invokes an action that requests some information from a Web Service (2 – 3) The app server sends the request to the Continuation Server and returns to the client-side, creating then an asynchronous situation / context (4 – 5) From the Continuation Server, a request is sent to the external Web Service (6 – 7) The Web Service processes a response that is sent back to the Continuation Server (8) This response is processed back to the App Server (9) And finally, sent back to the client-side to be displayed to the user To get into technical details of implementation, this is possible thanks to the definition of two different functions: The first one is responsible of creating, initializing and firing the Continuation (in the code examples of this document defined as startConinuation() function. It can receive parameters from the component) The second one is the callback: once the response has been processed and returned, we might want to work with the received data before displaying it to the user or to handle undesired exceptions from the External Service (in the code examples of this document this is defined as continuationCallback() function Why Apex continuations? So probably at this point, you are asking yourself: what’s the point of using these long-running asynchronous callouts, called Continuations, versus other asynchronous alternatives like for example, Queueable jobs? Well, you could use a Queueable job to perform a callout that will be executed asynchronously in the future, but you will lose the context of the execution. Even if you get the jobId, your component won’t have a callback to be executed once the job has finished. The Queueable jobs fit better in situations where the user don’t need to wait to see the results of the callout. Implementing + testing Apex continuations If you have already gone through the Salesforce Developer documentation, you might have seen that the implementation of Apex Continuation is not something new. Even more, there are already different official guides (very well explained), so let’s not reinvent the wheel! You can find below the official documentation for each context: Apex Continuation + Visualforce (Implementation + Testing) → Non-static methods Apex Continuation.+ Aura Components (Only implementation) → Static methods Apex Continuation + Lightning Web Components (Only implementation) → Static methods What’s the main difference between these three points above? The implementation in Visualforce Pages is done by using non-static methods (instances), while for Aura and LWC, the Continuations are executed from static methods (required when using the @AuraEnabled annotation). Apex Continuations were developed to be used through instances: making it possible to maintain individual references in an asynchronous context. When it comes to testing, the Test class implements a method invokeContinuationMethod(Object controller, Continuation request) that allows you to synchronously test your Apex Continuation by invoking the callback method for the specified controller. The first parameter of this function expects an instance of the controller class that implements the continuation and the second one is the continuation object that is returned by an action method in the controller class. When calling an Apex function from a LWC or an Aura Component, the Apex method must include the @AuraEnabled annotation and all the @AuraEnabled methods should always be static. When combined with the previous points… we can see that there is a problem: @AuraEnabled methods should always be static For Aura and LWC: our Continuation method will be a static function called from the component (because it will be implemented with the @AuraEnabled annotation). The Test.invokeContinuationMethod(controller, request) function is expecting an instance of the controller (non-static), but our class will be static with static methods PROBLEM: it is not possible to test the Apex Continuation implemented in a static context Another reason why this is not possible is because, in a testing context, all of the normally asynchronous functionality is executed synchronously (we don’t want to wait an undefined time to finish running our tests) and we don’t have stored anywhere a reference to the Continuation that was fired. SOLUTION: Nothing to worry about! I found a workaround to test the Apex Continuations by just implementing @TestVisible variables that will store relevant information to be used from the test methods, and replacing the Test.invokeContinuationMethod directly by your callback function. Also, as you might know, there are multiple ways to work with Apex Continuations: from one to multiple callouts within the same Continuation, to different implementations of the callback function. Here, the list of the examples is given below: Simple Continuation: one HttpCallout and callback(Object state) function Simple Continuation: one HttpCallout and labels, Object state) function Multiple Continuation: two HttpCallouts and labels, Object state) function Simple Apex continuation Only one Http callout One parameter in the callback: callback(Object state). There IS NO data being passed from the function that fired the continuation to the callback function ApexContinuation.cls '; // The function to be called to fire the continuation @AuraEnabled(continuation=true cacheable=true) public static Object startConinuation(){ // Create the callout Request HttpRequest req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(HTTP_REQUEST_ENDPOINT); // Create the continuation Continuation con = new Continuation(CONTINUATION_TIMEOUT_IN_SECONDS); con.ContinuationMethod = 'continuationCallback'; continuationState = con.addHttpRequest(req); // Store the HttRequest and make it accessible for a testing-context con.state = continuationState; return con; } // The function that will process the callback of the Continuation = 2000){ // Error codes available in: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_System_Continuation.htm // Continuation error throw new AuraHandledException('Continuation Error: ' + statusCode + ' - ' + response.getBody() ); }else{ // Http Request error throw new AuraHandledException('HTTP Request Error: ' + statusCode + ' - ' + response.getBody() ); } } } ApexContinuation_Test.cls @IsTest private class ApexContinuation_Test { // Case 1: Valid HttpRequest using a Continuation @IsTest private static void testApexContinuation(){ String mockResponseBody = 'TestBodyResponse'; Test.startTest(); requests = con.getRequests(); System.assertEquals(1, requests.size(), 'The number of requests associated to the Continuation is not correct'); // Create a mock response HttpResponse response = new HttpResponse(); response.setStatusCode(200); response.setBody(mockResponseBody); // Assign the Mock response to the variable created for testing - for keeping the reference to the correct Continuation Test.setContinuationResponse(ApexContinuation.continuationState, response); String result = (String)ApexContinuation.continuationCallback(ApexContinuation.continuationState); Test.stopTest(); System.assertEquals(mockResponseBody, result, 'Continuation failed: response body not valid'); } // Case 2: Exception caused by a problem with the continuation requests = con.getRequests(); System.assertEquals(1, requests.size(), 'The number of requests associated to the Continuation is not correct'); // Create a mock response HttpResponse response = new HttpResponse(); response.setStatusCode(2000); Test.setContinuationResponse(ApexContinuation.continuationState, response); Boolean exceptionThrown = false; try{ Object result = ApexContinuation.continuationCallback(ApexContinuation.continuationState); }catch(AuraHandledException e){ exceptionThrown = true; } Test.stopTest(); System.assertEquals(true, exceptionThrown, 'Failed to catch Continuation Exception'); } // Case 3: Exception caused by a bad Http Response requests = con.getRequests(); System.assertEquals(1, requests.size(), 'The number of requests associated to the Continuation is not correct'); // Create a mock response HttpResponse response = new HttpResponse(); response.setStatusCode(400); Test.setContinuationResponse(ApexContinuation.continuationState, response); Boolean exceptionThrown = false; try{ Object result = ApexContinuation.continuationCallback(ApexContinuation.continuationState); }catch(AuraHandledException e){ exceptionThrown = true; } Test.stopTest(); System.assertEquals(true, exceptionThrown, 'Failed to catch Http Request Exception'); } } Simple Apex continuation, passing data to the callback function Only one Http callout Two parameters in the callback: labels, Object state) There is data being passed from the function initiating the Continuation to the callback function ApexSimpleContinuationPassingData.cls public with sharing class ApexSimpleContinuationPassingData { @TestVisible private static String continuationLabel; // IMPORTANT: Variable used for testing, containing a reference to the Continuation Request @TestVisible private static String continuationState; // IMPORTANT: Variable used for testing, containing the value of the Continuation.state attribute private final static Integer CONTINUATION_TIMEOUT_IN_SECONDS = 40; private final static String HTTP_REQUEST_ENDPOINT = 'callout:TestEndpoint'; // The function to be called to fire the continuation @AuraEnabled(continuation=true cacheable=true) public static Object startConinuation(){ // Create the callout Request HttpRequest req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(HTTP_REQUEST_ENDPOINT); // Create the continuation Continuation con = new Continuation(CONTINUATION_TIMEOUT_IN_SECONDS); con.ContinuationMethod = 'continuationCallback'; continuationLabel = con.addHttpRequest(req); // Store the reference to the HttRequest and make it accessible for a test-context continuationState = 'Some data here...'; // Store data to be sent to the callback function con.state = continuationState; return con; } // The function that will process the callback of the Continuation= 2000){ // Continuation error throw new AuraHandledException('Continuation Error: ' + statusCode + ' - ' + response.getBody() ); }else{ // Http Request error throw new AuraHandledException('HTTP Request Error: ' + statusCode + ' - ' + response.getBody() ); } } } ApexSimpleContinuationPassingData_Test.cls @IsTest private class ApexSimpleContinuationPassingData_Test { // Case 1: Valid HttpRequest using a Continuation{ApexSimpleContinuationPassingData.continuationLabel}; String result = (String)ApexSimpleContinuationPassingData.continuationCallback(labels, ApexSimpleContinuationPassingData.continuationState); System.assertEquals(true, result.contains(mockResponseBody), 'Continuation failed: response body not valid'); System.assertEquals(true, result.contains('Some data here...')); } // Case 2: Exception caused by a problem with the continuation{ApexSimpleContinuationPassingData.continuationLabel}; Boolean exceptionThrown = false; try{ String result = (String)ApexSimpleContinuationPassingData.continuationCallback(labels, ApexSimpleContinuationPassingData.continuationState); }catch(AuraHandledException e){ exceptionThrown = true; } System.assertEquals(true, exceptionThrown, 'Failed to catch Continuation Exception'); } // Case 3: Exception caused by a bad Http Response{ApexSimpleContinuationPassingData.continuationLabel}; Boolean exceptionThrown = false; try{ String result = (String)ApexSimpleContinuationPassingData.continuationCallback(labels, ApexSimpleContinuationPassingData.continuationState); }catch(AuraHandledException e){ exceptionThrown = true; } System.assertEquals(true, exceptionThrown, 'Failed to catch Http Request Exception'); } } Multiple Apex continuation Two Http callouts (or more) Two parameters in the callback: labels, Object state) There might be data being passed from the function initiating the Continuation to the callback function ApexMultipleContinuation.cls public with sharing class ApexMultipleContinuation { // IMPORTANT: Variables used for testing, containing the values of the Continuation.state requests @TestVisible private static String continuationLabel1; @TestVisible private static String continuationLabel2; private final static Integer CONTINUATION_TIMEOUT_IN_SECONDS = 40; private final static String HTTP_REQUEST_ENDPOINT = 'callout:TestEndpoint'; // The function to be called to fire the continuation @AuraEnabled(continuation=true cacheable=true) public static Object startConinuation(){ // Create the callout Request HttpRequest req1 = new HttpRequest(); req1.setMethod('GET'); req1.setEndpoint(HTTP_REQUEST_ENDPOINT + '/2'); HttpRequest req2 = new HttpRequest(); req2.setMethod('GET'); req2.setEndpoint(HTTP_REQUEST_ENDPOINT + '/5'); // Create the continuation Continuation con = new Continuation(CONTINUATION_TIMEOUT_IN_SECONDS); con.ContinuationMethod = 'continuationCallback'; continuationLabel1 = con.addHttpRequest(req1); continuationLabel2 = con.addHttpRequest(req2); return con; } // The function that will process the callback of the Continuation{response1.getBody(), response2.getBody()}; } } ApexMultipleContinuation_Test.cls @IsTest private class ApexMultipleContinuation_Test { // Case 1: Valid HttpRequest using a Continuation)ApexMultipleContinuation.continuationCallback(labels, null); System.assertEquals(mockResponseBody1, result.get(0), 'Continuation failed: response body not valid for request 1'); System.assertEquals(mockResponseBody2, result.get(1), 'Continuation failed: response body not valid for request 2'); } // Case 2: Exception caused by a problem with the continuation) ApexMultipleContinuation.continuationCallback(labels, null); } catch (AuraHandledException e) { exceptionThrown = true; } System.assertEquals(true, exceptionThrown, 'Failed to catch Continuation Exception'); } // Case 3: Exception caused by a bad Http Response) ApexMultipleContinuation.continuationCallback(labels, null); } catch (AuraHandledException e) { exceptionThrown = true; } System.assertEquals(true, exceptionThrown, 'Failed to catch Http Request Exception'); } } Running the examples above in a LWC Do you want to try the examples above in a LWC? Here is the code: apexContinuation.js import { LightningElement } from 'lwc'; import startContinuation from '@salesforce/apexContinuation/'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; export default class ApexContinuation extends LightningElement { result; isLoading = true; // NOTE: The Continuation will be imperatively called after the component // is rendered. In your specific case, you might want to call at another // point of your code { this.displayErrorToast(error.body.message); this.isLoading = false; }); } displayErrorToast(message) { const evt = new ShowToastEvent({ message: message, variant: 'error', }); this.dispatchEvent(evt); } } apexContinuation.html Personal recommendations Finally, here are some tips and advice from what I have learned by implementing Apex Continuations: labels, Object state). This will help you have a standard development style in your code (and it is easier to work with the testing variables). When implementing Apex Continuations with multiple callouts, from my point of view, it’s better to create multiple @TestVisible variables (continuationLabelX) instead of a list in the Controller class. This will help you to really identify which label is referencing which request in the testing-context. Want to contribute or have suggestions to improve this workaround? Then don’t hesitate to reach out to me! About the author Víctor García Zarco is a Technical Consultant at Salesforce France. He has an IT Engineering and Business background, helps customers to implement Salesforce adapted to their needs and is an innovation and technology enthusiast. Check his GitHub projects @victorgz References Apex Developer Guide: Continuation Class Apex Developer Guide: Make Long-Running Callouts from a Visualforce Page Apex Developer Guide: Execution Governors and Limits Lightning Aura Components Developer Guide: @AuraEnabled Annotations for Continuations Lightning Web Components Developer Guide: Continuations
0 notes