java - 如何修复 java.lang.IllegalStateException : You have not started an Objectify context

标签 java google-app-engine servlets junit objectify

我正在尝试采用测试驱动开发方法来构建在 App Engine 上运行的基于 Java 的应用程序,但我在设置工作时遇到困难。

我的servlet

package mobi.grocerymonkey.groceryapp;

import com.google.appengine.api.utils.SystemProperty;

import java.io.IOException;
import java.io.BufferedReader;
import java.util.Properties;

import org.json.JSONObject;
import java.util.logging.Logger;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;

import static com.googlecode.objectify.ObjectifyService.ofy;
import com.googlecode.objectify.ObjectifyService;

/* This is the servlet */
@WebServlet(name = "GroceryServlet", value = "/grocery")
public class GroceryServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(GroceryServlet.class.getName());

  @Override
  public void init() throws ServletException {
    log.info("context init");
    ObjectifyService.init();
    ObjectifyService.register(Grocery.class);
  }

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {

    response.setContentType("text/plain");
    response.getWriter().println("Hello Kitty");
  }

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException {
      BufferedReader reader = request.getReader();
      String line = null;
      StringBuffer stringBuffer = new StringBuffer();
      while((line = reader.readLine()) != null) {
        stringBuffer.append(line);
      }
      String jsonString = stringBuffer.toString();
      JSONObject json = new JSONObject(jsonString);
      log.info("JSON "+ jsonString);

      Grocery grocery = new Grocery();
      grocery.setName((String) json.get("name"));
      grocery.setQuantity((Integer) json.get("quantity"));

      ofy().save().entity(grocery).now();

      log.info("JSON name "+ grocery.getName());

      response.setContentType("application/json");
      response.getWriter().println(jsonString);
    }

}

web.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <filter>
    <filter-name>ObjectifyFilter</filter-name>
    <filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>ObjectifyFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>FORWARD</dispatcher>
  </filter-mapping>
</web-app>

我的单元测试

package mobi.grocerymonkey.groceryapp;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;

import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.cloud.datastore.DatastoreOptions;

import com.google.cloud.datastore.DatastoreOptions;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.json.JSONObject;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.Reader;
import java.io.Closeable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Unit tests for {@link HelloAppEngine}.
 */
@RunWith(JUnit4.class)
public class GroceryServletTest {
  private static final String MOCK_URL = "/grocery";
  // Set up a helper so that the ApiProxy returns a valid environment for local testing.
  private final LocalServiceTestHelper helper =
    new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
  private Closeable closeable;

  @Mock
  private HttpServletRequest mockRequest;

  @Mock
  private HttpServletResponse mockResponse;

  private StringWriter responseWriter;
  private GroceryServlet servletUnderTest;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    helper.setUp();

    ObjectifyService.init(new ObjectifyFactory(
      DatastoreOptions.newBuilder()
        .setHost("http://localhost:8081")
        .setProjectId("enduring-trees-259812")
        .build()
        .getService()
      ));
      closeable = ObjectifyService.begin();

    //  Set up some fake HTTP requests
    when(mockRequest.getRequestURI()).thenReturn(MOCK_URL);

    JSONObject grocery = new JSONObject();
    grocery.put("name", "Beer");

    Reader inputString = new StringReader(grocery.toString());
    BufferedReader reader = new BufferedReader(inputString);
    when(mockRequest.getReader()).thenReturn(reader);

    // Set up a fake HTTP response.
    responseWriter = new StringWriter();
    when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));

    servletUnderTest = new GroceryServlet();
    servletUnderTest.init();
  }

  @After public void tearDown() throws Exception {
    closeable.close();
    helper.tearDown();
  }

  @Test
  public void doGetWritesResponse() throws Exception {
    servletUnderTest.doGet(mockRequest, mockResponse);

    // We expect our hello world response.
    assertThat(responseWriter.toString())
        .contains("Hello Kitty");
  }

  @Test
  public void doPostWritesResponse() throws Exception {
    JSONObject reqObj = new JSONObject();
    reqObj.put("name", "Beer");
    reqObj.put("quantity", 5);
    StringReader reader = new StringReader(reqObj.toString());

    when(mockRequest.getReader()).thenReturn(new BufferedReader(new StringReader(reqObj.toString())));

    servletUnderTest.doPost(mockRequest, mockResponse);

    // We expect our hello world response.
    assertThat(responseWriter.toString())
        .contains(reqObj.getString("name"));
  }
}

测试失败并显示以下错误消息

[ERROR] Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.103 s <<< FAILURE! - in mobi.grocerymonkey.groceryapp.GroceryServletTest [ERROR] doPostWritesResponse(mobi.grocerymonkey.groceryapp.GroceryServletTest) Time elapsed: 0.078 s <<< ERROR! java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method. at mobi.grocerymonkey.groceryapp.GroceryServletTest.doPostWritesResponse(GroceryServletTest.java:109)

这是由我的 servlet 中的 y().save().entity(grocery).now() 行引起的。当我删除它时,测试运行没有错误。

我尝试采用不同的方法来解决在 stackoverflow 上发现的此错误,但没有成功。

应该如何设置测试/应用程序才能使用测试驱动方法进行开发?我正在寻找一种能够首先编写单元测试然后编写实际应用程序的方法。但如何才能成功呢?

(免责声明,我已经有十多年没有使用 Java 了)

更新

ServletContext 文件

package mobi.grocerymonkey.groceryapp;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import java.io.Closeable;
import java.io.IOException;

import com.googlecode.objectify.ObjectifyService;
import static com.googlecode.objectify.ObjectifyService.ofy;

@WebListener
public class GroceryContextListener implements ServletContextListener {

    private ServletContext context;
    private Closeable closeable;

    public void contextInitialized(ServletContextEvent event) {
        this.context = event.getServletContext();

        ObjectifyService.init();
        this.closeable = ObjectifyService.begin();
        ObjectifyService.register(Grocery.class);
        System.out.println("Context initialized");
    }

    public void contextDestroyed(ServletContextEvent event) {
      try {
        this.closeable.close();
      } catch(IOException ioe) {

      }
    }
}

单元测试文件

package mobi.grocerymonkey.groceryapp;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;

import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.cloud.datastore.DatastoreOptions;

import com.google.cloud.datastore.DatastoreOptions;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.json.JSONObject;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.Reader;
import java.io.Closeable;
import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;

/**
 * Unit tests for {@link HelloAppEngine}.
 */
@RunWith(JUnit4.class)
public class GroceryServletTest {
  private static final String MOCK_URL = "/grocery";
  // Set up a helper so that the ApiProxy returns a valid environment for local testing.
  private final LocalServiceTestHelper helper =
    new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
  private Closeable closeable;

  @Mock
  private HttpServletRequest mockRequest;

  @Mock
  private HttpServletResponse mockResponse;

  private ServletContextListener contextListener;
  private ServletContext context;

  private StringWriter responseWriter;
  private GroceryServlet servletUnderTest;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    helper.setUp();

    contextListener = new GroceryContextListener();
    context = mock(ServletContext.class);

    //  Set up some fake HTTP requests
    when(mockRequest.getRequestURI()).thenReturn(MOCK_URL);

    JSONObject grocery = new JSONObject();
    grocery.put("name", "Beer");

    Reader inputString = new StringReader(grocery.toString());
    BufferedReader reader = new BufferedReader(inputString);
    when(mockRequest.getReader()).thenReturn(reader);

    // when(mockRequest.getServletContext()).thenReturn(context);

    // Set up a fake HTTP response.
    responseWriter = new StringWriter();
    when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));

    servletUnderTest = new GroceryServlet();
  }

  @After 
  public void tearDown() throws Exception {
    helper.tearDown();
  }

  @Test
  public void doGetWritesResponse() throws Exception {
    servletUnderTest.doGet(mockRequest, mockResponse);

    // We expect our hello world response.
    assertThat(responseWriter.toString())
        .contains("Hello Kitty");
  }

  @Test
  public void doPostWritesResponse() throws Exception {
    contextListener.contextInitialized(new ServletContextEvent(context));

    JSONObject reqObj = new JSONObject();
    reqObj.put("name", "Beer");
    reqObj.put("quantity", 5);
    StringReader reader = new StringReader(reqObj.toString());

    when(mockRequest.getReader()).thenReturn(new BufferedReader(new StringReader(reqObj.toString())));

    servletUnderTest.doPost(mockRequest, mockResponse);

    // We expect our hello world response.
    assertThat(responseWriter.toString())
        .contains(reqObj.getString("name"));
  }
}

Servlet 文件

package mobi.grocerymonkey.groceryapp;

import com.google.appengine.api.utils.SystemProperty;

import java.io.IOException;
import java.io.BufferedReader;
import java.util.Properties;

import org.json.JSONObject;
import java.util.logging.Logger;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;

import static com.googlecode.objectify.ObjectifyService.ofy;
import com.googlecode.objectify.ObjectifyService;

/* This is the servlet */
@WebServlet(name = "GroceryServlet", value = "/grocery")
public class GroceryServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(GroceryServlet.class.getName());

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {

    response.setContentType("text/plain");
    response.getWriter().println("Hello Kitty");
  }

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException {
      BufferedReader reader = request.getReader();
      String line = null;
      StringBuffer stringBuffer = new StringBuffer();
      while((line = reader.readLine()) != null) {
        stringBuffer.append(line);
      }
      String jsonString = stringBuffer.toString();
      JSONObject json = new JSONObject(jsonString);
      log.info("JSON "+ jsonString);

      Grocery grocery = new Grocery();
      grocery.setName((String) json.get("name"));
      grocery.setQuantity((Integer) json.get("quantity"));

      ofy().save().entity(grocery).now();

      log.info("JSON name "+ grocery.getName());

      response.setContentType("application/json");
      response.getWriter().println(jsonString);
    }

}

现在,我在运行测试时收到“com.google.cloud.datastore.DatastoreException:未经身份验证”错误,所以看起来我的方向是正确的。我是否会将数据存储凭据存储在 web.xml 中,然后将它们传递到类似于

的上下文
ObjectifyService.init(new ObjectifyFactory(
      DatastoreOptions.newBuilder()
        .setHost("http://localhost:8081")
        .setProjectId("enduring-trees-259812")
        .build()
        .getService()
      ));
    ObjectifyService.factory().register(Grocery.class);

新更新

我升级到Junit5并将整个测试重写为这个

package mobi.grocerymonkey.groceryapp;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.util.Closeable;
import static com.googlecode.objectify.ObjectifyService.factory;
import static com.googlecode.objectify.ObjectifyService.ofy;
import com.googlecode.objectify.Key;

import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.testing.LocalDatastoreHelper;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;

import mobi.grocerymonkey.groceryapp.util.TestBase;

import mobi.grocerymonkey.groceryapp.domain.Grocery;
import mobi.grocerymonkey.groceryapp.domain.GroceryList;

public class MyFirstTest extends TestBase {

  // Maximum eventual consistency.
  private final static LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
          .setDefaultHighRepJobPolicyUnappliedJobPercentage(100));

  Closeable closeable;

  @BeforeAll
  public static void setUp() {
    helper.setUp();
  }

  @AfterAll
  public static void tearDown() {
    helper.tearDown();
  }

  @BeforeEach
  public void setUpEach() {
    ObjectifyService.init(new ObjectifyFactory(
      DatastoreOptions.getDefaultInstance().getService()));
    closeable = ObjectifyService.begin();
  }

  @AfterEach
  public void tearDownEach() {
    closeable.close();
  }

  @DisplayName("Test MyFirstTest.testAddition()")
  @Test
  public void testAddition() {
    assertEquals(1 + 1, 2);
  }

  @DisplayName("Testing testGroceryList()")
  @Test
  public void testGroceryList() {
    factory().register(GroceryList.class);

    GroceryList list = new GroceryList("Weekend Beer List");
    Key<GroceryList> k1 = ofy().save().entity(list).now();

    assertEquals(1+1, 2);
  }
}

现在特意将其保存在一个文件中。但由于某种原因,数据存储无法找到运行测试时正在运行的模拟器。我收到数据存储未验证错误。

在运行单元测试之前,我运行了 gcloud beta emulators datastore start$(gcloud beta emulators datastore env-init)

最佳答案

问题是您调用了 ObjectifyService.init() 两次,但只在第一个(废弃的)工厂上调用了 begin()

您在 setUp() 方法中调用 init(),该方法初始化静态 ObjectifyFactory。然后,您可以使用 ObjectifyService.begin() 调用在该工厂上打开一个 session 。

setUp() 的末尾,您调用 servletUnderTest.init(),它还会调用 ObjectifyService.init()。这取代了静态ObjectifyFactory。当您下次执行 servlet 并调用 ofy()... 时,您使用的工厂尚未启动 session 。

看一下 ObjectifyService 的代码。从字面上看,只需几行代码即可包装静态 ObjectifyFactory 实例。

如果您有多个 servlet,则此代码在生产环境中也无法正常工作 - 您只想初始化并注册您的类一次。我建议使用 ServletContextListener 来执行此操作。

关于java - 如何修复 java.lang.IllegalStateException : You have not started an Objectify context,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59542836/

相关文章:

java - 如何创建按键事件

java - Google App Engine - 带有调度程序 Servlet 的 Blob 存储服务

java - 线程中的 Tomcat 死锁

java - BinarySearch 实现在某些情况下不起作用

java - 连接 Neo4j 时出现异常

java - 为什么 Eclipse 会提示死代码?

python - Google App Engine python,GQL,仅从数据存储中选择一列

python - 如何组织GAE Modules app结构和代码?

tomcat - 显示不同的网址

java - 此服务器上没有匹配的上下文