I need to write junit 4 test with full coverage. Let's start from method invoke but please take attention to cover also code inside getObject.
package com.pmi.aem.portalsite.core.config.contextaware;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.wcm.api.Page;
import com.pmi.aem.portalsite.core.models.PlaceholderModel;
import com.pmi.aem.portalsite.core.service.EndpointPlaceholderResolverService;
import com.pmi.aem.portalsite.core.util.ConfigUtils;

/**
 * Inspired by https://github.com/apache/sling-org-apache-sling-caconfig-impl/blob/99fd9421817f2943eba7e24d4156b78563af9756/src/main/java/org/apache/sling/caconfig/impl/ConfigurationProxy.java#L159
 */
public class CachingInvocationHandler implements InvocationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CachingInvocationHandler.class);

    private static final String PLACEHOLDER_OPEN = "[__";
    private static final String PLACEHOLDER_CLOSE = "__]";
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("([^\\[]*+)" + Pattern.quote(PLACEHOLDER_OPEN) + "(.+?)" + Pattern.quote(PLACEHOLDER_CLOSE) + "(.*+)");
    private static final Pattern PATH_GENERIC_PATTERN = Pattern.compile("\\p{IsLatin}");
    private static final Pattern PATH_CHANNEL_PATTERN = Pattern.compile("(\\{__pathChannel__\\})");
    private static final Pattern PATH_COUNTRY_PATTERN = Pattern.compile("(\\{__pathCountry__\\})");
    private static final Pattern PATH_LANG_PATTERN = Pattern.compile("(\\{__pathLang__\\})");
    private static final Pattern COUNTRY_LANG_PATTERN = Pattern.compile("(\\{__countryLang__\\})");
    private final Object originalObject;
    private final Page page;
    private final ResourceResolver resourceResolver;
    private final EndpointPlaceholderResolverService placeholderResolverService;
    private final PlaceholderModel placeholderModel;
    private final Map<String, Object> cache = new ConcurrentHashMap<>();

    private static final Object NULL_OBJECT = new Object();

    public CachingInvocationHandler(Object originalObject, Page page, ResourceResolver resourceResolver, EndpointPlaceholderResolverService endpointPlaceholderResolverService) {
        this.originalObject = originalObject;
        this.page = page;
        this.resourceResolver = resourceResolver;
        this.placeholderResolverService = endpointPlaceholderResolverService;
        this.placeholderModel = ConfigUtils.getPlaceholderList(resourceResolver, endpointPlaceholderResolverService.getEnvironmentLabels(ConfigUtils.getCountryCode(page)));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        LOGGER.trace("invoke: method {}", method);
        String key = method.getName();
        Object result = cache.get(key);
        if (result == null) {
            result = getObject(method, args);
            if (result == null) {
                result = NULL_OBJECT;
            }
            cache.put(key, result);
        }
        if (result == NULL_OBJECT) {
            return null;
        }
        return result;
    }

    private @Nullable Object getObject(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
        Object value = method.invoke(originalObject, args);
        LOGGER.trace("getObject: processing method: {}", method);

        if (value != null) {
            if (value instanceof String) {
                value = removePlaceholders((String) value);
            }
            Class<?> componentType = value.getClass().getComponentType();
            if (value.getClass().isArray() && componentType.getName().startsWith("com.pmi.aem.portalsite.core.config")) {
                LOGGER.trace("getObject: value {} is array of CA class", value);
                int length = Array.getLength(value);
                Object newArray = Array.newInstance(componentType, length);
                for (var i = 0; i < length; i++) {
                    Object element = Array.get(value, i);
                    Object proxiedElement = createManipulatedProxy(element, componentType, page, resourceResolver, placeholderResolverService);
                    Array.set(newArray, i, proxiedElement);
                }
                value = newArray;
            }
            if (value.getClass().isArray() && componentType == String.class) {
                LOGGER.trace("getObject: value {} is array of String class", value);
                int length = Array.getLength(value);
                String[] newArray = new String[length];
                for (var i = 0; i < length; i++) {
                    String element = (String) Array.get(value, i);
                    newArray[i] = removePlaceholders(element);
                }
                value = newArray;
            }

            String name = value.getClass().getName();
            if (name.contains("com.pmi.aem.portalsite.core.config") || name.contains("com.sun.proxy")) {
                LOGGER.trace("getObject: value {} is CA class or Proxy class", value);
                return createManipulatedProxy(value, method.getReturnType(), page, resourceResolver, placeholderResolverService);
            }
        }
        LOGGER.trace("getObject: returning value of method {} without intervention", method);
        return value;
    }

    public static <T> Object createManipulatedProxy(Object item, Class<T> aClass, Page page, ResourceResolver resourceResolver, EndpointPlaceholderResolverService endpointPlaceholderResolverService) {
        LOGGER.trace("createManipulatedProxy: item {}, class: {}", item, aClass);
        if (aClass.isAnnotation() || aClass.getName().contains("com.sun.proxy")) {
            CachingInvocationHandler cachingInvocationHandler = new CachingInvocationHandler(item, page, resourceResolver, endpointPlaceholderResolverService);
            return Proxy.newProxyInstance(
                    // Sonar raise issue java:S3032 suggesting to use Thread.currentThread().getContextClassLoader()
                    // instead of aClass.getClassLoader() but this change will break implementation at runtime
                    aClass.getClassLoader(),
                    new Class<?>[]{aClass},
                    cachingInvocationHandler
            );
        }
        return item;
    }

    String removePathPlaceholders(String value) {
        LOGGER.trace("removePathPlaceholders: {}", value);
        var matcher = PATH_GENERIC_PATTERN.matcher(value);
        if (matcher.find()) {
            final Page langPage = page.getAbsoluteParent(3);
            value = PATH_LANG_PATTERN.matcher(value).replaceAll(langPage.getPath());
            final Page countryPage = page.getAbsoluteParent(2);
            value = PATH_COUNTRY_PATTERN.matcher(value).replaceAll(countryPage.getPath());
            final Page channelPage = page.getAbsoluteParent(1);
            value = PATH_CHANNEL_PATTERN.matcher(value).replaceAll(channelPage.getPath());
            var countryLang = String.format("%s/%s", countryPage.getName(), langPage.getName());
            value = COUNTRY_LANG_PATTERN.matcher(value).replaceAll(countryLang);
        }
        return value;
    }

    String removePlaceholders(String value) {
        LOGGER.trace("removePlaceholders: {}", value);
        String result = value;
        if (placeholderModel != null && StringUtils.isNotBlank(value)) {
            Map<String, String> environmentLabels = placeholderModel.getPlaceholderMap();
            Matcher matcher = PLACEHOLDER_PATTERN.matcher(result);
            if (matcher.matches()) {
                for (Map.Entry<String, String> entry : environmentLabels.entrySet()) {
                    String key = String.format("%s%s%s", PLACEHOLDER_OPEN, entry.getKey(), PLACEHOLDER_CLOSE);
                    result = value.contains(key) ? result.replace(key, entry.getValue()) : result;
                }
            }
            result = removePathPlaceholders(result);
        }
        return result;
    }
}