ClientLocalizationManager error

I am writing a simple SpinnerComponent class that uses a PMISpinner:

package com.inductiveautomation.ignition.examples.ce.components;

import com.inductiveautomation.factorypmi.application.components.PMISpinner;

import javax.swing.*;
import java.awt.*;


public class SpinnerComponent extends JComponent{

    private final double MAX_VALUE = 120.0;
    private final Dimension dims = new Dimension(60, 20);

    public SpinnerComponent() {
        PMISpinner spinner = new PMISpinner();
        spinner.setSpinnerMode(PMISpinner.INT_SPINNER);
        spinner.setMaxValue(MAX_VALUE);
        spinner.setMinimumSize(dims);
    }

}

Test File:

import com.inductiveautomation.ignition.examples.ce.components.SpinnerComponent;
import org.junit.jupiter.api.Test;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SpinnerComponentTest {


    @Test
    public void testSpinnerComponentDisplay() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        SpinnerComponent spinnerComponent = new SpinnerComponent();
        frame.add(spinnerComponent);

        frame.pack();
        frame.setVisible(true);

        int displayDuration = 10000;
        Timer timer = new Timer(displayDuration, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                frame.dispose();
            }
        });
        timer.setRepeats(false);
        timer.start();

        try {
            Thread.sleep(displayDuration);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

    }
}

I am getting a weird error. This is the surfire test report:

-------------------------------------------------------------------------------
Test set: SpinnerComponentTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.303 sec <<< FAILURE!
SpinnerComponentTest.testSpinnerComponentDisplay()  Time elapsed: 0.301 sec  <<< FAILURE!
java.lang.NullPointerException: Cannot invoke "com.inductiveautomation.ignition.client.model.ClientLocalizationManager.getCurrentLocale()" because the return value of "com.inductiveautomation.ignition.client.model.ClientLocalizationManager.get()" is null
	at com.inductiveautomation.factorypmi.application.components.PMISpinner.getCurrentLocale(PMISpinner.java:109)
	at com.inductiveautomation.factorypmi.application.components.PMISpinner.<init>(PMISpinner.java:90)
	at com.inductiveautomation.ignition.examples.ce.components.SpinnerComponent.<init>(SpinnerComponent.java:15)
	at SpinnerComponentTest.testSpinnerComponentDisplay(SpinnerComponentTest.java:16)


I am using Windows 10 and an IntelliJ development environment. I tried editing the Custom VM options in IntelliJ with these to change the Java locale settings.

-Duser.language=en
-Duser.country=US
-Duser.variant=US

But it still did not work.
Any ideas?

As the stacktrace alludes, PMISpinner's init is calling getCurrentLocale, which is attempting to reach out to the singleton ClientLocalizationManager object that is assumed to be present. There's... a lot of that kind of stuff; it's not ideal from a unit testing standpoint, but a shortcut/technical debt that's piled up over fifteen years of Vision existing.

If you really need to unit test your own GUI components, I would suggest, in order of preference:

  1. Don't unit test Swing GUI components.
  2. Don't unit test any Swing components that contain base Vision components. There are lots of more 'pure' Swing components available in client-api, but by embedding the exact same component used in Vision, you're basically guaranteeing that you have to have a "like-for-like" environment to unit test in.
  3. Mock ClientLocalizationManager...and probably other things, that are implicitly relied upon by components like PMISpinner.

The reason I suggest "don't unit test Swing GUI components" is because, in my experience, unit testing GUIs invariably ends up pointless. You spend more time maintaining test cases that don't actually guarantee you anything, when the thing you want to test is business logic, which should be wrapped up in 'pure' functions. But that is just my opinion :slight_smile:

2 Likes

Side note: Creating a local variable in your constructor of type PMISpinner doesn't magically make a PMISpinner part of your component--it will be thrown away at the end of the constructor.

3 Likes