org.dbflute.utflute.lastaflute.WebContainerTestCase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utflute-lastaflute Show documentation
Show all versions of utflute-lastaflute Show documentation
The unit test library for LastaFlute (with DBFlute)
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.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.dbflute.helper.function.IndependentProcessor;
import org.dbflute.utflute.lastaflute.mock.MockResopnseBeanValidator;
import org.dbflute.utflute.lastaflute.mock.MockRuntimeFactory;
import org.dbflute.utflute.lastaflute.mock.TestingHtmlData;
import org.dbflute.utflute.lastaflute.mock.TestingJsonData;
import org.dbflute.utflute.lastaflute.validation.TestingValidationErrorAfter;
import org.dbflute.utflute.mocklet.MockletHttpServletRequest;
import org.dbflute.utflute.mocklet.MockletHttpServletRequestImpl;
import org.dbflute.utflute.mocklet.MockletHttpServletResponse;
import org.dbflute.utflute.mocklet.MockletHttpServletResponseImpl;
import org.dbflute.utflute.mocklet.MockletHttpSession;
import org.dbflute.utflute.mocklet.MockletServletConfig;
import org.lastaflute.core.direction.FwAssistantDirector;
import org.lastaflute.core.magic.ThreadCacheContext;
import org.lastaflute.core.message.MessageManager;
import org.lastaflute.di.core.ExternalContext;
import org.lastaflute.di.core.LaContainer;
import org.lastaflute.di.core.factory.SingletonLaContainerFactory;
import org.lastaflute.meta.DocumentGenerator;
import org.lastaflute.meta.SwaggerGenerator;
import org.lastaflute.meta.agent.yourswagger.YourSwaggerSyncAgent;
import org.lastaflute.meta.agent.yourswagger.YourSwaggerSyncOption;
import org.lastaflute.meta.exception.YourSwaggerDiffException;
import org.lastaflute.meta.swagger.web.LaActionSwaggerable;
import org.lastaflute.web.LastaWebKey;
import org.lastaflute.web.response.ActionResponse;
import org.lastaflute.web.response.HtmlResponse;
import org.lastaflute.web.response.JsonResponse;
import org.lastaflute.web.ruts.process.ActionRuntime;
import org.lastaflute.web.servlet.request.RequestManager;
import org.lastaflute.web.token.DoubleSubmitManager;
import org.lastaflute.web.token.DoubleSubmitTokenMap;
import org.lastaflute.web.validation.exception.ValidationErrorException;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
/**
* The base class of test cases for web environment with DI container.
* You can use tests of LastaFlute components e.g. action, assist, logic, job.
*
* Standard application structure:
*
* WebContainerTestCase
* |-Unit[App]TestCase
* |-[Your]ActionTest
*
*
* You can test like this:
*
* public void test_yourMethod() {
* // ## Arrange ##
* YourAction action = new YourAction();
* inject(action);
*
* // ## Act ##
* action.submit();
*
* // ## Assert ##
* assertTrue(action...);
* }
*
* @author jflute
* @since 0.5.1 (2015/03/22 Sunday)
*/
public abstract class WebContainerTestCase extends LastaFluteTestCase {
// ===================================================================================
// Attribute
// =========
// -----------------------------------------------------
// Request Mock
// ------------
/** The mock request of the test case execution. (NullAllowed: when no web mock or beginning or ending) */
private MockletHttpServletRequest _xmockRequest;
/** The mock response of the test case execution. (NullAllowed: when no web mock or beginning or ending) */
private MockletHttpServletResponse _xmockResponse;
// -----------------------------------------------------
// LastaFlute Component
// --------------------
@Resource
private FwAssistantDirector _assistantDirector;
@Resource
private MessageManager _messageManager;
@Resource
private RequestManager _requestManager;
@Resource
private DoubleSubmitManager _doubleSubmitManager;
// ===================================================================================
// Settings
// ========
// -----------------------------------------------------
// Prepare Container
// -----------------
@Override
protected void xprepareTestCaseContainer() {
super.xprepareTestCaseContainer();
if (!isSuppressRequestMock()) {
xdoPrepareRequestMockContext();
}
}
/**
* Does it suppress web-request mock? e.g. HttpServletRequest, HttpSession
* @return The determination, true or false.
*/
protected boolean isSuppressRequestMock() {
return false;
}
protected void xdoPrepareRequestMockContext() {
// the servletConfig has been already created when container initialization
final MockletServletConfig servletConfig = xgetCachedServletConfig();
if (servletConfig != null) { // basically true, just in case (e.g. might be overridden)
xregisterRequestMockContext(servletConfig);
}
}
protected void xregisterRequestMockContext(MockletServletConfig servletConfig) { // like S2ContainerFilter
final LaContainer container = SingletonLaContainerFactory.getContainer();
final ExternalContext externalContext = container.getExternalContext();
final MockletHttpServletRequest request = createMockletHttpServletRequest(servletConfig.getServletContext());
final MockletHttpServletResponse response = createMockletHttpServletResponse(request);
externalContext.setRequest(request);
externalContext.setResponse(response);
xkeepMockRequestInstance(request, response); // for web mock handling methods
}
protected MockletHttpServletRequest createMockletHttpServletRequest(ServletContext servletContext) {
return new MockletHttpServletRequestImpl(servletContext, prepareMockServletPath());
}
protected MockletHttpServletResponse createMockletHttpServletResponse(HttpServletRequest request) {
return new MockletHttpServletResponseImpl(request);
}
protected String prepareMockServletPath() { // you can override
return "/utservlet";
}
protected void xkeepMockRequestInstance(MockletHttpServletRequest request, MockletHttpServletResponse response) {
_xmockRequest = request;
_xmockResponse = response;
}
@Override
protected boolean maybeContainerResourceOverridden() {
return super.maybeContainerResourceOverridden() || xisMethodOverridden("prepareMockServletPath");
}
// -----------------------------------------------------
// Destroy Container
// -----------------
@Override
protected void xdestroyTestCaseContainer() {
xclearRequestMockContext();
super.xdestroyTestCaseContainer();
}
protected void xclearRequestMockContext() {
final LaContainer container = SingletonLaContainerFactory.getContainer();
final ExternalContext externalContext = container.getExternalContext();
if (externalContext != null) { // just in case
externalContext.setRequest(null);
externalContext.setResponse(null);
}
xreleaseMockRequestInstance();
}
protected void xreleaseMockRequestInstance() {
_xmockRequest = null;
_xmockResponse = null;
}
// ===================================================================================
// Request Mock
// ============
// -----------------------------------------------------
// LastaFlute
// ----------
protected ActionRuntime getMockHtmlRuntime() { // MockAction@sea()
return new MockRuntimeFactory().createHtmlRuntime();
}
protected ActionRuntime getMockJsonRuntime() { // MockAction@land()
return new MockRuntimeFactory().createJsonRuntime();
}
// -----------------------------------------------------
// Request
// -------
protected MockletHttpServletRequest getMockRequest() {
return _xmockRequest;
}
protected void addMockRequestHeader(String name, String value) {
final MockletHttpServletRequest request = getMockRequest();
if (request != null) {
request.addHeader(name, value);
}
}
@SuppressWarnings("unchecked")
protected ATTRIBUTE getMockRequestParameter(String name) {
final MockletHttpServletRequest request = getMockRequest();
return request != null ? (ATTRIBUTE) request.getParameter(name) : null;
}
protected void addMockRequestParameter(String name, String value) {
final MockletHttpServletRequest request = getMockRequest();
if (request != null) {
request.addParameter(name, value);
}
}
@SuppressWarnings("unchecked")
protected ATTRIBUTE getMockRequestAttribute(String name) {
final MockletHttpServletRequest request = getMockRequest();
return request != null ? (ATTRIBUTE) request.getAttribute(name) : null;
}
protected void setMockRequestAttribute(String name, Object value) {
final MockletHttpServletRequest request = getMockRequest();
if (request != null) {
request.setAttribute(name, value);
}
}
// -----------------------------------------------------
// Response
// --------
protected MockletHttpServletResponse getMockResponse() {
return _xmockResponse;
}
protected Cookie[] getMockResponseCookies() {
final MockletHttpServletResponse response = getMockResponse();
return response != null ? response.getCookies() : null;
}
protected int getMockResponseStatus() {
final MockletHttpServletResponse response = getMockResponse();
return response != null ? response.getStatus() : 0;
}
protected String getMockResponseString() {
final MockletHttpServletResponse response = getMockResponse();
return response != null ? response.getResponseString() : null;
}
// -----------------------------------------------------
// Session
// -------
/**
* @return The instance of mock session. (NotNull: if no session, new-created)
*/
protected MockletHttpSession getMockSession() {
return _xmockRequest != null ? (MockletHttpSession) _xmockRequest.getSession(true) : null;
}
protected void invalidateMockSession() {
final MockletHttpSession session = getMockSession();
if (session != null) {
session.invalidate();
}
}
@SuppressWarnings("unchecked")
protected ATTRIBUTE getMockSessionAttribute(String name) {
final MockletHttpSession session = getMockSession();
return session != null ? (ATTRIBUTE) session.getAttribute(name) : null;
}
protected void setMockSessionAttribute(String name, Object value) {
final MockletHttpSession session = getMockSession();
if (session != null) {
session.setAttribute(name, value);
}
}
// ===================================================================================
// Response Validation
// ===================
/**
* Validate HTML data, evaluating HTML bean's validator annotations.
*
* // ## Act ##
* HtmlResponse response = action.index(form);
*
* // ## Assert ##
* TestingHtmlData htmlData = validateHtmlData(response);
* htmlData.requiredList("beans", ProductBean.class).forEach(bean -> {
* assertEquals("...", bean.productName);
* });
*
* @param response The HTML response to be validated. (NotNull)
* @return The HTML data for testing. (NotNull)
*/
protected TestingHtmlData validateHtmlData(HtmlResponse response) {
return new MockResopnseBeanValidator(_requestManager).validateHtmlData(response);
}
/**
* Validate JSON data, evaluating JSON result's validator annotations.
*
* // ## Act ##
* JsonResponse<ProductRowResult> response = action.index(form);
*
* // ## Assert ##
* TestingJsonData<ProductRowResult> jsonData = validateJsonData(response);
* ProductRowResult result = jsonData.getJsonResult();
* ...
*
* @param The type of JSON bean.
* @param response The HTML response to be validated. (NotNull)
* @return The HTML data for testing. (NotNull)
*/
protected TestingJsonData validateJsonData(JsonResponse response) {
return new MockResopnseBeanValidator(_requestManager).validateJsonData(response);
}
// ===================================================================================
// Validation Error
// ================
/**
* Assert validation error of action.
*
* // ## Arrange ##
* SignupAction action = new SignupAction();
* inject(action);
* SignupForm form = new SignupForm();
* // ## Act ##
* assertValidationError(() -> action.index(form)).handle(data -> {
* // ## Assert ##
* data.requiredMessageOf("sea", Required.class);
* });
*
* @param noArgInLambda The callback for calling methods that should throw the validation error exception. (NotNull)
* @return The after object that has handler of expected cause for chain call. (NotNull)
*/
protected TestingValidationErrorAfter assertValidationError(IndependentProcessor noArgInLambda) {
final Set causeSet = new HashSet();
assertException(ValidationErrorException.class, () -> noArgInLambda.process()).handle(cause -> causeSet.add(cause));
return new TestingValidationErrorAfter(causeSet.iterator().next(), _messageManager, _requestManager);
}
/**
* Evaluate validation error hook for action response.
*
* // if HTML response
* assertException(ValidationErrorException.class, () -> action.update(form)).handle(cause -> {
* HtmlResponse response = hookValidationError(cause);
* TestingHtmlData htmlData = validateHtmlData(response);
* ...
* });
*
* @param The type of action response, e.g. HtmlResponse, JsonResponse.
* @param cause The exception of validation error. (NotNull)
* @return The action response from validation error hook.
* @deprecated use assertValidationError()
*/
@SuppressWarnings("unchecked")
protected RESPONSE hookValidationError(ValidationErrorException cause) {
return (RESPONSE) cause.getErrorHook().hook();
}
// ===================================================================================
// Token Assertion
// ===============
/**
* Assert double submit token is saved in action execution.
*
* // ## Act ##
* HtmlResponse response = action.index(memberId); // calls saveToken()
*
* // ## Assert ##
* assertTokenSaved(action.getClass());
*
* @param groupType The group type to get double submit token, basically action type. (NotNull)
*/
protected void assertTokenSaved(Class> groupType) { // for action that calls saveToken()
final DoubleSubmitTokenMap tokenMap = _doubleSubmitManager.getSessionTokenMap().get();
final boolean condition = tokenMap.get(groupType).isPresent();
assertTrue("Not found the transaction token saved in session, so call saveToken(): tokenMap=" + tokenMap, condition);
}
/**
* Mock double submit token is requested in the test process.
*
* // ## Arrange ##
* MemberEditAction action = new MemberEditAction();
* inject(action);
* mockTokenRequested(action.getClass());
* ...
*
* // ## Act ##
* HtmlResponse response = action.update(form); // calls verifyToken()
*
* // ## Assert ##
* assertTokenVerified();
*
* @param groupType The group type to get double submit token, basically action type. (NotNull)
*/
protected void mockTokenRequested(Class> groupType) { // for action that calls verityToken()
final String savedToken = _doubleSubmitManager.saveToken(groupType);
getMockRequest().setParameter(LastaWebKey.TRANSACTION_TOKEN_KEY, savedToken);
}
/**
* Mock double submit token is requested as double submit in the test process.
*
* // ## Arrange ##
* MemberEditAction action = new MemberEditAction();
* inject(action);
* mockTokenRequestedAsDoubleSubmit(action.getClass());
* ...
*
* // ## Act ##
* // ## Assert ##
* assertException(DoubleSubmittedRequestException.class, () -> action.update(form));
*
* @param groupType The group type to get double submit token, basically action type. (NotNull)
*/
protected void mockTokenRequestedAsDoubleSubmit(Class> groupType) { // for action that calls verityToken()
final String savedToken = _doubleSubmitManager.saveToken(groupType);
getMockRequest().setParameter(LastaWebKey.TRANSACTION_TOKEN_KEY, savedToken);
_doubleSubmitManager.verifyToken(groupType, () -> { // means first request done
throw new IllegalStateException("no way");
});
}
/**
* Assert double submit token is verified in action execution.
*
* // ## Arrange ##
* MemberEditAction action = new MemberEditAction();
* inject(action);
* mockTokenRequested(action.getClass());
* ...
*
* // ## Act ##
* HtmlResponse response = action.update(form); // calls verityToken()
*
* // ## Assert ##
* assertTokenVerified();
*
*/
protected void assertTokenVerified() { // for action that calls verityToken()
final boolean condition = _doubleSubmitManager.isFirstSubmittedRequest();
assertTrue("Not found the transaction token verification, so call verifyToken().", condition);
}
// ===================================================================================
// Mock HTML Validation
// ====================
/**
* Mock HTML validate() call for thread cache adjustment.
* For example, in LastaRemoteApi, client error translation of HTML validation error needs this.
*
* // in yourDefaultRule()
* rule.translateClientError(resource -> {
* ...
* return resource.asHtmlValidationError(messages);
* });
*
* ...
*
* // in unit test
* mockHtmlValidateCall();
* assertValidationError(() -> bhv.requestProductList(param));
*
*/
protected void mockHtmlValidateCall() { // for e.g. remote api unit test
ThreadCacheContext.registerValidatorErrorHook(() -> ActionResponse.undefined()); // dummy
}
// ===================================================================================
// LastaDoc
// ========
/**
* Save meta data for rich LastaDoc.
*
* 1. call this method in your unit test
* 2. execute FreeGen task (manage.sh 12) of DBFlute
* 3. see auto-generated LastaDoc
*
*/
protected void saveLastaDocMeta() {
createDocumentGenerator().saveLastaDocMeta();
}
/**
* Create document generator for rich LastaDoc.
* @return The new-created document generator. (NotNull)
*/
protected DocumentGenerator createDocumentGenerator() {
return new DocumentGenerator();
}
// ===================================================================================
// Swagger
// =======
/**
* Save meta data to use rich swagger in war deployment.
*
* public void test_swaggerJson() {
* saveSwaggerMeta(new SwaggerAction());
* }
*
* @param swaggerable The new-created swagger-able action to get swagger JSON. (NotNull)
*/
protected void saveSwaggerMeta(LaActionSwaggerable swaggerable) {
assertNotNull(swaggerable);
inject(swaggerable);
createSwaggerGenerator().saveSwaggerMeta(swaggerable);
}
/**
* Create swagger generator for rich swagger.
* @return The new-created swagger generator. (NotNull)
*/
protected SwaggerGenerator createSwaggerGenerator() {
return new SwaggerGenerator();
}
/**
* Verify that your swagger.json is synchronized with source codes.
* Basically for manual-made swagger.json driven development.
* @param locationPath The location path to your swagger.json, can be resource path and filesystem path. (NotNull)
* @param opLambda The callback for your swagger-sync option. (NotNull)
* @throws YourSwaggerDiffException When your swagger.json has differences with source codes.
*/
protected void verifyYourSwaggerSync(String locationPath, Consumer opLambda) {
createYourSwaggerSyncAgent().verifyYourSwaggerSync(locationPath, opLambda);
}
/**
* Create agent for your swagger.json synchronization.
* @return The new-created agent. (NotNull)
*/
protected YourSwaggerSyncAgent createYourSwaggerSyncAgent() {
return new YourSwaggerSyncAgent();
}
// ===================================================================================
// Accessor
// ========
protected MockletHttpServletRequest xgetMockRequest() {
return _xmockRequest;
}
protected void xsetMockRequest(MockletHttpServletRequest xmockRequest) {
_xmockRequest = xmockRequest;
}
protected MockletHttpServletResponse xgetMockResponse() {
return _xmockResponse;
}
protected void xsetMockResponse(MockletHttpServletResponse xmockResponse) {
_xmockResponse = xmockResponse;
}
}