Unit Test to verify number of times lazy initialization logic is executed for a data member
Delegate the computation of a and b to a method, say, .compute(). Since we want to spy this method's execution, we will suppose that we have Guava and its useful @VisibleForTesting annotation, and make the method protected:

@VisibleForTesting
protected String compute(String a, String b) { // code here }


Now, code written using Mockito and TestNG (with test file in same package than class A, so that the compute() method is visible):

@Test
public void computationOfCHappensOnlyOnce()
{
    final String a = "whatever";
    final String b = "you want";

    final A spy = spy(new A(a, b));

    for (int i = 0; i &lt; 10; i++)
        spy.getC();

    verify(spy, times(1)).compute(eq(a), eq(b));
}


Note: the way your class is currently written, it is NOT thread safe. The quick, coarse way to fix it is to make .getC() synchronized.

EDIT Discussion on a different design to get the value of c

This requires a creation of a new factory class for instances of class A; it would have the responsibility to create new instances and compute c:

// if the class is a singleton...
final FactoryForA factory = FactoryForA.getInstance();

final A a = factory.newA(a, b);

// This calls back to the factory to compute c:
a.getC();


The A class needs to implement .equals() and .hashCode() (on fields a and b but obviously not c!). Also, it has a constructure which is now package private (so that the factory can access it) and whose prototype is:

final String a, b;
final FactoryForA factory;
// No `c` member, see below

A(String a, String b, FactoryForA factory) 
{
    this.a = a; this.b = b; this.factory = factory;
}

public getC()
{
    return factory.computeC(this);
}


Now for the factory itself: it has an ExecutorService and a Map&lt;A, FutureTask&lt;String&gt;&gt;. Here is the code of computeC():

// instance variables of the factory
private final ExecutorService service
    = Executors.newFixedThreadPool(5); // for example
private final Map&lt;A, FutureTask&lt;String&gt;&gt; map = new HashMap&lt;&gt;();

// package private
String computeC(final A a)
{
    FutureTask&lt;String&gt; task;
    // Synchronized map access
    synchronized(map) {
        // If there is no task created for that instance of A, create it
        // and submit it for execution
        task = map.get(a);
        if (task == null) {
            task = createFutureTask(a); // TODO, but nothing too complicated
            // IMPORTANT! If you don't do this, the task is not executed
            // and .get() below will block indefinitely
            service.execute(task);
            map.put(a, task);
        }
    }
    // A FutureTask will keep its status! We can therefore call .get()
    // as much as we like; the computation will only have happened once
    return task.get();
}


One advantage is that if two instances created for A are equal (ie, the same a and b parameters), computation for c happens only once!