If you have a controller plus VF page that could be subject to viewstate issues and you want to avoid introducing viewstate issues once you have already tested some released version of your code, how should you go about doing this?
There is (as of V36.0), no PageReference
method getViewStateSize()
which would be the obvious choice.
So, is there a way to approximate the viewstate and verify it doesn’t grow as more data volume increases?
First Principles: What is Viewstate comprised of?
Viewstate is used to preserve state across HTTP stateless protocol as the page interacts with the controller via GETs and POSTs (at least until a client-side redirect occurs). From the Salesforce Developer Doc, Viewstate is made up of:
- All non-transient data members in the associated controller (either standard or custom) and the controller extensions.
- Objects that are reachable from a non-transient data member in a controller or controller extension.
- The component tree for that page, which represents the page’s component structure and the associated state, which are the values applied to those components.
- A small amount of data for Visualforce to do housekeeping.
So, from a testmethod point of view, we have access to the first two: non-transient data members and their reachable objects. The component tree will vary in size depending on how many components are rendered – and this typically varies with the number of list elements in apex:pageBlockTables
, apex:dataTables
, and apex:repeats
. The Visualforce housekeeping space consumption is not available to us.
Steps to take
Assuming we have followed the excellent guidelines in Visualforce in Practice, Chapter 13, Visualforce Performance and Best PracticesRapidfire Rendering of Visualforce Pages, what remains is to use the Apex testmethods as a regression suite to early warn you if you have introduced a viewstate regression, at least from the controller side.
Note, there are ways to avoid Viewstate altogether via client-side techniques such as Javascript Remoting but for purposes of this Blog post, let’s assume you have a traditional VF controller + VF markup.
Here’s a coding approach to use in your testmethod:
@isTest private static void testViewState() { // Mock n Sobjects MyController ctlr = new Controller(); // same principle applies for extensions // exercise setters, getters, and ajax action methods that process all n records Integer ctlrSerializedSize = Json.serialize(ctlr); // get size of controller, serialized // mock another n SObjects so we have 2n total ctlr = new Controller(); // exercise setters, getters, and ajax action methods that process all 2n records System.assertEquals(ctlrSerializedSize,Json.serialize(ctlr), 'non transient size of controller should stay flat'); }
Caveats
- For Json.serialize to work, you can’t have any non-transient, unserializable variables like
System.SelectOption
. Use methods rather than getter properties. A list of restrictions can be found in the Apex Developer Doc – JSON Support. Some things may be hard to work around. - The equality assert given in the code sample most likely works only if the same input conditions are used for the test on n records as is used on 2n records.
- I’ll say it again, this won’t test the number of VF apex:components generated by 2n versus n — that could blow up your view state. Hopefully you addressed this the first time with your design by following the aforementioned VF in Practice guidelines, Chapter 13.