【问题标题】:How to fix java.lang.IllegalStateException: You have not started an Objectify context如何修复 java.lang.IllegalStateException:您尚未启动 Objectify 上下文
【发布时间】:2020-04-19 21:47:51
【问题描述】:

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

我的小服务程序

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"));
  }
}

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

[错误] 测试运行:2,失败:0,错误:1,跳过:0,时间 经过:0.103 秒

这是由我的 servlet 中的这一行 ofy().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: Unauthenticated”错误,所以看起来我走对了。我是否会将数据存储凭据存储在 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);
  }
}

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

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

【问题讨论】:

    标签: java google-app-engine servlets junit objectify


    【解决方案1】:

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

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

    在您的setUp() 末尾调用servletUnderTest.init(),它也调用ObjectifyService.init()。这将替换静态 ObjectifyFactory。当您下一次执行您的 servlet 并调用 ofy()... 时,您正在使用一个尚未启动会话的工厂。

    查看ObjectifyService 的代码。包装静态ObjectifyFactory 实例只需几行代码。

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

    【讨论】:

    • 谢谢,我根据我对您的建议的理解更新了帖子。是的,将有超过 1 个 servlet 访问数据存储区
    • 不要在您的ServletContextListener 中调用begin()。在您的测试工具中,您需要从根本上执行与容器相同的操作 - 除了您需要使用数据存储服务调用它而不是 ObjectifyService.init()(无参数)之外。您几乎可以肯定每次测试运行都需要一个新的 init(),但在代码之外还需要一个 begin()...。假装你是 ObjectifyFilter。
    • 你知道我可以检查的任何好的参考吗?我还没有找到任何显示如何将 objectify 与 servlet 一起使用
    • 我不能直接指出任何事情,但这是 Java 101 的东西 - 查看 ObjectifyService 的 impl,您应该能够弄清楚如何围绕它构建测试工具。或者,查看 Objectify 源代码中的 Junit5 工具 - 您的应用程序应该做几乎完全相同的事情。
    • 当我查看github.com/objectify/objectify/wiki/Setup 时,它告诉我使用上下文监听器来初始化 ObjectifyService。我知道在单元测试中完成它可能会有所不同,但仍然可能是 101,但有一些文档会很好:) 而且在查看 github.com/objectify/objectify/blob/… 时也不清楚
    【解决方案2】:

    测试驱动开发的核心围绕五个步骤展开,您在整个软件开发生命周期中重复了这些步骤。

    测试驱动的开发生命周期:

    1. 编写测试

    2. 运行测试(没有实现代码,测试不通过)

    3. 编写足够的实现以使测试通过

    4. 运行所有测试(测试通过)

    5. 重构

    6. 重复

    按照这些步骤,您可以为您的应用程序创建一个 TDD 实现。

    除了我上面指定的步骤之外,Google Cloud 没有具体的方法。

    如您的错误中所述,您可以看到您尚未启动 Objectify 上下文,并且您缺少 ObjectifyFilter。

    这是 Java 列表中的implementation,它遵循 TDD,可能有助于消除您的一些顾虑。

    【讨论】:

    • 我得到了那部分,但我找不到一个例子来告诉我如何解决这个问题
    • 您可以使用@Before/@After 结合ObjectifyService.begin() 来启动请求。但是,这实际上可能不是您的测试的工作方式。一个脏东西,你可以这样伪造它:*我的@BeforeMethod启动一个对象化上下文(ObjectifyService.begin())*我的@AfterMethod关闭对象化上下文*我用来完成()请求的那段代码现在只需关闭现有上下文并打开一个新上下文。
    • 我在 @Before 方法中有 ObjectifyService.begin(),但我仍然遇到同样的错误
    猜你喜欢
    • 2015-02-27
    • 2020-04-28
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多