How to resolve "You have not started an Objectify context" in JUnit?
Asked Answered
T

6

12

I've got some Objectify test code running in JUnit and I'm getting this 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 com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
    at com.googlecode.objectify.impl.ref.LiveRef.<init>(LiveRef.java:31)
    at com.googlecode.objectify.Ref.create(Ref.java:26)
    at com.googlecode.objectify.Ref.create(Ref.java:32)
    at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImpl.create(DownloadTaskRepositoryImpl.java:35)
    at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImplTest.setUp(DownloadTaskRepositoryImplTest.java:45)

How do I resolve this for test code?

Transcalent answered 31/12, 2014 at 22:40 Comment(0)
T
10

Jeff Schnitzer answered this here: https://groups.google.com/forum/#!topic/objectify-appengine/8HinahG7irg. That link points to https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk where Jeff suggests the following quick and dirty workaround:

  • My @BeforeMethod starts an objectify context (ObjectifyService.begin())

  • My @AfterMethod closes the objectify context

Jeff suggests we use ObjectifyService.run() instead but admits it's more work.

Here's how my implementation looks:

public class DownloadTaskRepositoryImplTest {
    // maximum eventual consistency (see https://cloud.google.com/appengine/docs/java/tools/localunittesting)
    private final LocalServiceTestHelper helper =
        new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
            .setDefaultHighRepJobPolicyUnappliedJobPercentage(100));

    private Closeable closeable;

    @Before
    public void setUp() {
        helper.setUp();
        ObjectifyRegistrar.registerDataModel();
        closeable = ObjectifyService.begin();
    }

    @After
    public void tearDown() {
        closeable.close();

        helper.tearDown();
    }
Transcalent answered 31/12, 2014 at 22:40 Comment(3)
@Michael...where did you get the "ObjectifyRegistrar?"Hippocrene
This solution just can't work, "ObjectifyRegistrar" doesn't existSheol
ObjectifyRegistrar presumably means calling ObjectifyService.register().Sandpiper
P
10

I also had this issue and noticed that I had not added the ObjectifyFilter to my web.xml

<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>
</filter-mapping>

I also had to include Objectify and guava jars in my WEB-INF>lib directory and include them in my build path.

Purvis answered 2/12, 2015 at 11:21 Comment(1)
This may be my problem. Where does this go in web.xml??Maladroit
P
1

I was facing the same error and this solusion worked for me

I have an app based on Endpoints that uses Objectify. When I leave it with the default/automatic scaling, everything works great. Once I enable basic scaling, though, I get the following exception when executing the endpoint method:

[INFO] 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.
[INFO]  at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
[INFO]  at com.myco.myapp.dao.datastore.OfyService.ofy(OfyService.java:62)

The good news is that this goes away when you enable RequestDispatcher support in the web.xml file like so. I think this is a documentation issue, then, but I didn't know if everyone would agree if I edited the Wiki page directly. Here is the proposed web.xml entry, which worked for me:

   <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>
Publicity answered 17/3, 2017 at 12:54 Comment(0)
S
0

Improving michael-osofsky answer, I add this to my ofy helper class

public static void registerDataModel() {
    try {
        factory().register(Profile.class);
    } catch (Exception e){
        e.printStackTrace();
    }
}

and remplace

ObjectifyRegistrar.registerDataModel();

for this

OfyService.registerDataModel();

OfyService.java

public static void registerDataModel() {
    try {
        factory().register(Profile.class);
    } catch (Exception e){
        e.printStackTrace();
    }
}
Segura answered 30/5, 2017 at 2:29 Comment(0)
C
0

As Jeff Schnitzer says in the link provided by Michael Osofsky:

In your tests you should have some notion of a 'request' even if it is just conceptual. If "each test is a request by itself", then you can use @Before/@After in conjunction with ObjectifyService.begin() to demarcate the requests. However, this is probably not actually how your tests work - it isn't how my tests work.

He then goes on to say:

This would be prettier with JDK8 closures but the idea is straightforward - you're wrapping some unit of work in a context which represents a request. It would probably be smart to add even more context like authentication in that wrapper too.

I came up with the following implementation of his idea. With the solution below, you can ensure each call to a servlet handler gets a fresh Objectify session while still making your servlet handler calls in a single line of code. It also decouples your tests from explicitly worrying about Objectify, and allows you to add additional non-Objectify context around your servlet handlers.

My solution below works with Objectify 5.1.22. I tried using Objectify 6+, but I had problems that seem to be related to this.

First, define a custom Supplier that is able to capture the exceptions thrown by a servlet handler.

  @FunctionalInterface
  public interface ServletSupplier<T> {

  T get()
    throws ServletException, IOException;
  }

Next, define a wrapper method that accepts your new custom Supplier as an input, and wrap the call to ServletSupplier.get() in a try-with-resources block that calls ObjectifyService.begin(). You must also register your entity classes before calling ServletSupplier.get().

  public <T> T runInServletContext(ServletSupplier<T> servletMethod)
      throws ServletException, IOException {

    try (Closeable session = ObjectifyService.begin()) {
      ObjectifyService.register(MyObj.class);
      return servletMethod.get();
    }
  }

Finally, anywhere in your tests that you call the servlet handler you should do so using the wrapper method.

  MyObj myObjPost = runInServletContext(() -> getServlet().doPost(request, response));
  // Assert results of doPost call.
  MyObj myObjGet = runInServletContext(() -> getServlet().doGet(request, response));
  // Assert results of doGet call.
Clericalism answered 17/2, 2019 at 20:50 Comment(0)
M
0

Just in case someone ends up here (as I originally did) looking up the same problem but for the ktor "main.kt" server instead of unit tests...

After looking at the ObjectifyFilter source code, I added

val closer = ObjectifyService.begin()
... real service here ...
closer.close()

around my actual servlet code and that fixed the problem.

Modlin answered 4/2, 2023 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.