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

org.dbflute.utflute.lastaflute.LastaFluteTestCase Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024 the original author or authors.
 *
 * 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.dbflute.utflute.lastaflute;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.sql.DataSource;

import org.dbflute.hook.AccessContext;
import org.dbflute.utflute.lastadi.LastaDiTestCase;
import org.dbflute.utflute.lastaflute.mail.MailMessageAssertion;
import org.dbflute.utflute.lastaflute.mail.TestingMailData;
import org.dbflute.utflute.mocklet.MockletServletConfig;
import org.dbflute.utflute.mocklet.MockletServletConfigImpl;
import org.dbflute.utflute.mocklet.MockletServletContext;
import org.dbflute.utflute.mocklet.MockletServletContextImpl;
import org.dbflute.util.DfTypeUtil;
import org.lastaflute.core.direction.FwAssistantDirector;
import org.lastaflute.core.json.JsonManager;
import org.lastaflute.core.magic.ThreadCacheContext;
import org.lastaflute.core.magic.destructive.BowgunDestructiveAdjuster;
import org.lastaflute.core.time.SimpleTimeManager;
import org.lastaflute.db.dbflute.accesscontext.PreparedAccessContext;
import org.lastaflute.di.core.factory.SingletonLaContainerFactory;
import org.lastaflute.web.LastaFilter;
import org.lastaflute.web.response.JsonResponse;

import jakarta.annotation.Resource;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;

/**
 * The base class of test cases with LastaFlute (also with Lasta Di).
 * @author jflute
 * @since 0.8.6 (2018/04/20 Friday)
 */
public abstract class LastaFluteTestCase extends LastaDiTestCase {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    /** The cached configuration of servlet. (NullAllowed: when no web mock or beginning or ending) */
    private static MockletServletConfig _xcachedServletConfig;

    protected static Boolean _xexistsLastaJob; // lazy-loaded for performance
    protected static boolean _xjobSchedulingSuppressed;

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    /** The (main) data source for database. (NotNull: after injection) */
    @Resource
    private DataSource _xdataSource;

    // -----------------------------------------------------
    //                                       Mail Validation
    //                                       ---------------
    private MailMessageAssertion _xmailMessageAssertion;

    // ===================================================================================
    //                                                                            Settings
    //                                                                            ========
    // -----------------------------------------------------
    //                                                Set up
    //                                                ------
    @Override
    protected boolean xisSuppressTestCaseAccessContext() {
        return true; // instead, use context geared by transaction
    }

    @Override
    protected void xsetupBeforeTestCaseContainer() {
        xsuppressJobSchedulingIfNeeds();
        super.xsetupBeforeTestCaseContainer();
    }

    @Override
    protected void xsetupAfterTestCaseContainer() {
        super.xsetupAfterTestCaseContainer();
        if (isUseJobScheduling()) {
            xrebootJobSchedulingIfNeeds();
        }
        initializeAssistantDirector(); // nearly actual timing
    }

    protected void initializeAssistantDirector() { // injection not yet here
        final FwAssistantDirector director = getComponent(FwAssistantDirector.class);
        director.assistCoreDirection().assistCurtainBeforeHook().hook(director);
    }

    @Override
    protected void xsetupBeforeTestCaseInjection() {
        super.xsetupBeforeTestCaseInjection();
        initializeThreadCacheContext(); // nearly actual timing
        initializePreparedAccessContext();
    }

    protected void initializeThreadCacheContext() {
        ThreadCacheContext.initialize();
    }

    protected void initializePreparedAccessContext() {
        PreparedAccessContext.setAccessContextOnThread(createPreparedAccessContext());
    }

    protected AccessContext createPreparedAccessContext() {
        return createTestCaseAccessContext(); // you should change to your context
    }

    // -----------------------------------------------------
    //                                             Tear Down
    //                                             ---------
    @Override
    protected void postTest() {
        super.postTest();
        xprocessMailAssertion();
    }

    @Override
    public void tearDown() throws Exception {
        xdestroyJobSchedulingIfNeeds(); // always destroy if scheduled to avoid job trouble
        super.tearDown();
        ThreadCacheContext.clear(); // should be after closing transaction for e.g. LazyTransaction
        if (BowgunDestructiveAdjuster.hasAnyBowgun()) {
            BowgunDestructiveAdjuster.unlock();
            BowgunDestructiveAdjuster.restoreBowgunAll();
        }
    }

    // -----------------------------------------------------
    //                                     Prepare Container
    //                                     -----------------
    @Override
    protected boolean isUseOneTimeContainer() { // may be overridden
        return maybeContainerResourceOverridden(); // to destroy cache but #hope separate life-cycle
    }

    protected boolean maybeContainerResourceOverridden() {
        return xisMethodOverridden("prepareMockContextPath");
    }

    protected boolean xisMethodOverridden(String methodName, Class... argTypes) {
        try {
            getClass().getDeclaredMethod(methodName, argTypes);
            return true;
        } catch (NoSuchMethodException ignored) {
            return false;
        }
    }

    @Override
    protected boolean xisTreatedAsWebContainer() {
        return true; // fixedly (not related to request mock)
    }

    // -----------------------------------------------------
    //                                  Initialize Container
    //                                  --------------------
    @Override
    protected void xinitializeContainer(String configFile) {
        log("...Initializing Lasta Di via LastaFlute: " + configFile);
        xdoInitializeContainerViaLastaFlute(configFile);
    }

    protected void xdoInitializeContainerViaLastaFlute(String configFile) {
        SingletonLaContainerFactory.setConfigPath(configFile);
        final ServletConfig servletConfig = xprepareMockServletConfig(configFile);
        final LastaFilter filter = xcreateLastaFilter();
        try {
            filter.init(new FilterConfig() { // initializing Lasta Di
                public String getFilterName() {
                    return "containerFilter";
                }

                public ServletContext getServletContext() {
                    return servletConfig.getServletContext();
                }

                public Enumeration getInitParameterNames() {
                    return null;
                }

                public String getInitParameter(String name) {
                    return null;
                }
            });
        } catch (ServletException e) {
            String msg = "Failed to initialize servlet config to servlet: " + servletConfig;
            throw new IllegalStateException(msg, e.getRootCause());
        }
    }

    // -----------------------------------------------------
    //                                     Destroy Container
    //                                     -----------------
    @Override
    protected void xdestroyContainer() {
        super.xdestroyContainer();
        xclearServletConfig();
    }

    // -----------------------------------------------------
    //                                          Servlet Mock
    //                                          ------------
    protected ServletConfig xprepareMockServletConfig(String configFile) {
        _xcachedServletConfig = createMockletServletConfig(); // cache for request mocks
        _xcachedServletConfig.setServletContext(createMockletServletContext());
        return _xcachedServletConfig;
    }

    protected LastaFilter xcreateLastaFilter() {
        return new LastaFilter();
    }

    protected MockletServletConfig createMockletServletConfig() {
        return new MockletServletConfigImpl();
    }

    protected MockletServletContext createMockletServletContext() {
        return new MockletServletContextImpl(prepareMockContextPath());
    }

    protected String prepareMockContextPath() { // you can override
        return "/utcontext";
    }

    protected void xclearServletConfig() {
        _xcachedServletConfig = null;
    }

    // ===================================================================================
    //                                                                         Cannon-ball
    //                                                                         ===========
    @Override
    protected void xprepareCannonballBeginning() {
        super.xprepareCannonballBeginning();
        initializeThreadCacheContext();
    }

    @Override
    protected void xprepareCannonballAccessContext() {
        initializePreparedAccessContext(); // not call super not to use default
    }

    // ===================================================================================
    //                                                                       JSON Handling
    //                                                                       =============
    /**
     * Show JSON string for the JSON bean.
     * @param jsonBean The JSON bean to be serialized. (NotNull)
     */
    protected void showJson(Object jsonBean) {
        log(jsonBean.getClass().getSimpleName() + ":" + ln() + toJson(jsonBean));
    }

    /**
     * Convert to JSON string from the JSON bean.
     * @param jsonBean The JSON bean to be serialized. (NotNull)
     * @return The JSON string. (NotNull)
     */
    protected String toJson(Object jsonBean) {
        final Object realBean;
        if (jsonBean instanceof JsonResponse) {
            realBean = ((JsonResponse) jsonBean).getJsonBean();
        } else {
            realBean = jsonBean;
        }
        return getComponent(JsonManager.class).toJson(realBean);
    }

    // ===================================================================================
    //                                                                      Mail Assertion
    //                                                                      ==============
    /**
     * Reserve mail assertion, should be called before action execution.
     * 
     * // ## Arrange ##
     * SignupAction action = new SignupAction();
     * inject(action);
     * SignupForm form = new SignupForm();
     * reserveMailAssertion(data -> {
     *     data.required(WelcomeMemberPostcard.class).forEach(message -> {
     *        message.requiredToList().forEach(addr -> {
     *            assertContains(addr.getAddress(), form.memberAccount); // e.g. [email protected]
     *        });
     *        message.assertPlainTextContains(form.memberName);
     *        message.assertPlainTextContains(form.memberAccount);
     *     });
     * });
     *
     * // ## Act ##
     * HtmlResponse response = action.signup(form);
     * ...
     * 
* @param dataLambda The callback for mail data. (NotNull) */ protected void reserveMailAssertion(Consumer dataLambda) { _xmailMessageAssertion = new MailMessageAssertion(dataLambda); } protected void xprocessMailAssertion() { if (_xmailMessageAssertion != null) { _xmailMessageAssertion.assertMailData(); _xmailMessageAssertion = null; } } // =================================================================================== // Destructive // =========== // ----------------------------------------------------- // Asynchronous // ------------ /** * Change asynchronous process to normal synchronous, to be easy to assert.
* (Invalidate AsyncManager) *
     * // ## Arrange ##
     * changeAsyncToNormalSync();
     * ...
     * 
*/ protected void changeAsyncToNormalSync() { BowgunDestructiveAdjuster.unlock(); BowgunDestructiveAdjuster.shootBowgunAsyncToNormalSync(); } /** * Restore asynchronous process to normal synchronous.
* (async() is executed as asynchronous) */ protected void restoreAsyncToNormalSync() { BowgunDestructiveAdjuster.unlock(); BowgunDestructiveAdjuster.restoreBowgunAsyncToNormalSync(); } // ----------------------------------------------------- // Transaction // ----------- /** * Change requires-new transaction to required transaction, to be easy to assert.
* (All transactions can be in test transaction) *
     * // ## Arrange ##
     * changeRequiresNewToRequired();
     * ...
     * 
*/ protected void changeRequiresNewToRequired() { BowgunDestructiveAdjuster.unlock(); BowgunDestructiveAdjuster.shootBowgunRequiresNewToRequired(); } /** * Restore requires-new transaction to required transaction.
* (requiresNew() is executed as requires-new) */ protected void restoreRequiresNewToRequired() { BowgunDestructiveAdjuster.unlock(); BowgunDestructiveAdjuster.restoreBowgunRequiresNewToRequired(); } // ----------------------------------------------------- // Current Date // ------------ // to be geared with LastaFlute @Override protected void switchCurrentDate(Supplier dateTimeSupplier) { super.switchCurrentDate(dateTimeSupplier); SimpleTimeManager.unlock(); SimpleTimeManager.shootBowgunCurrentTimeProvider(() -> { return DfTypeUtil.toDate(dateTimeSupplier.get()).getTime(); }); } @Override protected void xclearSwitchedCurrentDate() { if (xisUseSwitchedCurrentDate()) { SimpleTimeManager.unlock(); SimpleTimeManager.shootBowgunCurrentTimeProvider(null); } super.xclearSwitchedCurrentDate(); } // =================================================================================== // LastaJob // ======== protected void xsuppressJobSchedulingIfNeeds() { if (!xexistsLastaJob()) { return; } if (_xjobSchedulingSuppressed) { // to avoid duplicate calls when batch execution of unit test return; } _xjobSchedulingSuppressed = true; try { // reflection on parade not to depends on LastaJob library final Class jobManagerType = Class.forName("org.lastaflute.job.SimpleJobManager"); final Method unlockMethod = jobManagerType.getMethod("unlock", (Class[]) null); unlockMethod.invoke(null, (Object[]) null); final Method shootMethod = jobManagerType.getMethod("shootBowgunEmptyScheduling", (Class[]) null); shootMethod.invoke(null, (Object[]) null); } catch (Exception continued) { log("*Failed to suppress job scheduling", continued); } } protected boolean isUseJobScheduling() { // you can override, for e.g. heavy scheduling (using e.g. DB) return false; // you can set true only when including LastaJob } protected void xrebootJobSchedulingIfNeeds() { // called when isUseJobScheduling() if (!xexistsLastaJob()) { return; } try { // reflection on parade not to depends on LastaJob library final Class jobManagerType = xforNameJobManager(); final Object jobManager = getComponent(jobManagerType); if (!xisJobSchedulingDone(jobManagerType, jobManager)) { xcallNoArgInstanceJobMethod(jobManagerType, jobManager, "reboot"); } } catch (Exception continued) { log("*Failed to reboot job scheduling", continued); } } protected void xdestroyJobSchedulingIfNeeds() { // always called from tearDown() if (!xexistsLastaJob()) { return; } try { // reflection on parade not to depends on LastaJob library final Class jobManagerType = xforNameJobManager(); if (hasComponent(jobManagerType)) { // e.g. in classpath and include lasta_job.xml final Object jobManager = getComponent(jobManagerType); if (xisJobSchedulingDone(jobManagerType, jobManager)) { xcallNoArgInstanceJobMethod(jobManagerType, jobManager, "destroy"); } } } catch (Exception continued) { log("*Failed to destroy job scheduling", continued); } } protected static Class xforNameJobManager() throws ClassNotFoundException { return Class.forName("org.lastaflute.job.JobManager"); } protected boolean xisJobSchedulingDone(Class jobManagerType, Object jobManager) throws ReflectiveOperationException { return (boolean) xcallNoArgInstanceJobMethod(jobManagerType, jobManager, "isSchedulingDone"); } private Object xcallNoArgInstanceJobMethod(Class jobManagerType, Object jobManager, String methodName) throws ReflectiveOperationException { final Method rebootMethod = jobManagerType.getMethod(methodName, (Class[]) null); return rebootMethod.invoke(jobManager, (Object[]) null); } protected boolean xexistsLastaJob() { if (_xexistsLastaJob != null) { return _xexistsLastaJob; } try { xforNameJobManager(); _xexistsLastaJob = true; // this method is called outside container so cannot determine it //_xexistsLastaJob = hasComponent(jobManagerType); } catch (ClassNotFoundException e) { _xexistsLastaJob = false; } return _xexistsLastaJob; } // =================================================================================== // JDBC Helper // =========== /** {@inheritDoc} */ @Override protected DataSource getDataSource() { // user method return _xdataSource; } // =================================================================================== // Accessor // ======== protected static MockletServletConfig xgetCachedServletConfig() { return _xcachedServletConfig; } protected static void xsetCachedServletConfig(MockletServletConfig xcachedServletConfig) { _xcachedServletConfig = xcachedServletConfig; } protected MailMessageAssertion xgetMailMessageValidator() { return _xmailMessageAssertion; } protected void xsetMailMessageValidator(MailMessageAssertion xmailMessageValidator) { _xmailMessageAssertion = xmailMessageValidator; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy