Как проверить, что Singleton-экземпляр инициализирован единожды?

«Как проверить, что Singleton-экземпляр инициализирован единожды?» — вопрос из категории Паттерны проектирования, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для проверки однократной инициализации Singleton в тестах я использую несколько подходов, в зависимости от реализации паттерна.

1. Проверка ссылочной идентичности:

@Test
public void getInstance_ShouldReturnSameObject() {
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();

    assertSame("Оба вызова должны возвращать один и тот же объект", 
               instance1, instance2);
}

2. Проверка через рефлексию (если нужно убедиться в однократном вызове конструктора):

@Test
public void constructor_ShouldBeCalledOnlyOnce() throws Exception {
    // Сброс инстанса перед тестом (если есть такая возможность)
    resetSingletonInstance();

    // Первый вызов
    Singleton instance1 = Singleton.getInstance();

    // Попытка создать второй экземпляр через рефлексию
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);

    assertThrows(IllegalStateException.class, () -> {
        Singleton instance2 = constructor.newInstance();
    }, "Конструктор должен запрещать создание второго экземпляра");
}

3. Для потокобезопасных реализаций (Double-Checked Locking):

@Test
public void getInstance_ThreadSafe_ShouldReturnSameObject() throws InterruptedException {
    final int threadCount = 10;
    final CountDownLatch latch = new CountDownLatch(threadCount);
    final Set<Singleton> instances = Collections.synchronizedSet(new HashSet<>());

    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            instances.add(Singleton.getInstance());
            latch.countDown();
        }).start();
    }

    latch.await(5, TimeUnit.SECONDS);
    assertEquals(1, instances.size(), "Все потоки должны получить один и тот же экземпляр");
}

Ключевые моменты тестирования Singleton:

  • Проверка, что повторные вызовы getInstance() возвращают тот же объект
  • Для ленивой инициализации — проверка, что объект создается только при первом вызове
  • Для потокобезопасных реализаций — тестирование в многопоточной среде
  • Проверка сериализации/десериализации (если Singleton реализует Serializable)
  • Использование мок-фреймворков для изоляции тестов, если Singleton имеет сложные зависимости