All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simplify4u.slf4jmock.mockito.LoggerAnnotationEngine Maven / Gradle / Ivy

/*
 * Copyright 2020 Slawomir Jaranowski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.simplify4u.slf4jmock.mockito;

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.internal.configuration.InjectingAnnotationEngine;
import org.mockito.internal.util.reflection.Fields;
import org.mockito.internal.util.reflection.InstanceField;
import org.simplify4u.slf4jmock.LoggerMock;
import org.simplify4u.slf4jmock.MDCMock;
import org.simplify4u.slf4jmock.ProxyMock;
import org.simplify4u.slf4jmock.SimpleLogger;
import org.slf4j.Logger;
import org.slf4j.spi.MDCAdapter;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Extension for Mockito Annotation Engine.
 * 

* Shouldn't be used directly. */ @SuppressWarnings("java:S1312") // Loggers should be "private static final" and should share a naming convention public final class LoggerAnnotationEngine extends InjectingAnnotationEngine { private static final Logger FAKE_LOGGER = new SimpleLogger(""); @Override public AutoCloseable process(Class clazz, Object testInstance) { InstanceField spyToSet = prepareSpyLogger(testInstance); AutoCloseable processResult = super.process(clazz, testInstance); Optional mdcMoc = findMDCMock(testInstance); mdcMoc.ifPresent(MDCMock::setMock); Map loggersMocks = findLoggersMocks(testInstance, spyToSet); loggersMocks.forEach(LoggerMock::setMock); return () -> { processResult.close(); MDCMock.clearMock(); loggersMocks.keySet().forEach(LoggerMock::clearMock); }; } private static InstanceField prepareSpyLogger(Object testInstance) { List spyMocks = Fields.allDeclaredFieldsOf(testInstance).instanceFields() .stream() .filter(field -> field.jdkField().getType() == Logger.class) .filter(field -> field.isAnnotatedBy(Spy.class)) .collect(Collectors.toList()); // find uninitialized spy logger List nullSpy = spyMocks.stream() .filter(field -> field.read() == null) .collect(Collectors.toList()); // set fake logger for spy // in next step real spy will be prepared nullSpy.forEach(field -> field.set(FAKE_LOGGER)); return nullSpy.stream().findFirst().orElse(null); } private static Optional findMDCMock(Object testInstance) { List mdcFields = Fields.allDeclaredFieldsOf(testInstance).instanceFields() .stream() .filter(instanceField -> instanceField.jdkField().getType() == MDCAdapter.class) .filter(instanceField -> instanceField.isAnnotatedBy(Mock.class)) .collect(Collectors.toList()); if (mdcFields.size() > 1) { throw new MockRuntimeException("Class: " + testInstance.getClass() + "\n" + "contains more then one mock for MDCAdapter\n\n" + mdcFields.stream() .map(InstanceField::name) .map(n -> " @Mock\n MDCAdapter " + n + ";") .collect(Collectors.joining("\n\n")) + "\n\nonly one or zero is allowed per unit test"); } return mdcFields.stream().findFirst().map(InstanceField::read).map(MDCAdapter.class::cast); } private static List findClassUnderTest(Object testInstance) { return Fields.allDeclaredFieldsOf(testInstance).instanceFields() .stream() .filter(instanceField -> instanceField.isAnnotatedBy(InjectMocks.class)) .collect(Collectors.toList()); } private static Map findLoggersMocks(Object testInstance, InstanceField spyToSet) { Map loggerMocks = Fields.allDeclaredFieldsOf(testInstance).instanceFields() .stream() .filter(field -> field.jdkField().getType() == Logger.class) .filter(field -> field.isAnnotatedBy(Mock.class) || field.isAnnotatedBy(Spy.class)) .collect(Collectors.toMap(LoggerAnnotationEngine::getLoggerName, LoggerAnnotationEngine::getLoggerFromFiled, LoggerAnnotationEngine::mockLoggerMerge)); if (!loggerMocks.containsKey("")) { // all mock have name return loggerMocks; } List classesUnderTest = findClassUnderTest(testInstance); if (classesUnderTest.isEmpty()) { throw new MockRuntimeException("We have unnamed Logger for @Mock or @Spy" + " we need at lease one class under test with @InjectMock in order to discover Logger name"); } List loggersUnderTest = classesUnderTest.stream() .map(InstanceField::read) .map(Fields::allDeclaredFieldsOf) .map(Fields.InstanceFields::instanceFields) .flatMap(List::stream) .filter(field -> field.jdkField().getType() == Logger.class) .collect(Collectors.toList()); if (loggersUnderTest.isEmpty()) { throw new MockRuntimeException("Classes under test doesn't have defined Logger"); } if (loggersUnderTest.size() > 1) { throw new MockRuntimeException("Classes under test has define to many Loggers\n" + "We need one Logger defined in all class under test for unnamed @Mock or @Spy"); } InstanceField instanceField = loggersUnderTest.get(0); String loggerName = ((ProxyMock) instanceField.read()).getMockName(); Logger oldLogger = loggerMocks.remove(""); Logger newLogger; if (Mockito.mockingDetails(oldLogger).isSpy()) { // spy Logger without name ... we must create new newLogger = Mockito.spy(new SimpleLogger(loggerName)); Optional.ofNullable(spyToSet).ifPresent(field -> field.set(newLogger)); } else { newLogger = oldLogger; } loggerMocks.put(loggerName, newLogger); return loggerMocks; } private static Logger mockLoggerMerge(Logger l1, Logger l2) { throw new MockRuntimeException("Logger Mock must have unique name, " + " but we have: " + l1 + " and " + l2 + "\n" + "please define like:\n @Mock(name=\"logger.name\")"); } private static String getLoggerName(InstanceField field) { String name = Optional.ofNullable(field.annotation(Mock.class)) .map(Mock::name) .orElse(null); if (name == null) { // get name from spy logger Optional logger = Optional.ofNullable(field.read()).map(Logger.class::cast); name = logger.map(Logger::getName).orElse(null); logger.ifPresent(Mockito::clearInvocations); } return name; } private static Logger getLoggerFromFiled(InstanceField field) { return (Logger) field.read(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy