Objectify error "You cannot create a Key for an object with a null @Id" in JUnit
Asked Answered
H

2

1

I got the following error while testing a simple piece of code in JUnit that creates a User object (an Objectify Entity) and then tries to attach it as a Parent to another Objectify Entity called DownloadTask:

java.lang.IllegalArgumentException: You cannot create a Key for an object with a null @Id. Object was com.netbase.followerdownloader.model.User@57fcbecc
    at com.googlecode.objectify.impl.KeyMetadata.getRawKey(KeyMetadata.java:185)
    at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:35)
    at com.googlecode.objectify.impl.Keys.keyOf(Keys.java:28)
    at com.googlecode.objectify.Key.create(Key.java:62)
    at com.googlecode.objectify.Ref.create(Ref.java:31)
    at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImpl.create(DownloadTaskRepositoryImpl.java:35)
    at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImplTest.setUp(DownloadTaskRepositoryImplTest.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Here's essentially what the code looks like:

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 DownloadTaskRepositoryImpl downloadTaskRepositoryImpl;

    @Before
    public void setUp() {
        helper.setUp();
        DatastoreServiceFactory.getDatastoreService();

        (new ObjectifyRegistrar()).registerDataModel();

        UserRepositoryImpl userRepositoryImpl = new UserRepositoryImpl();
        User user = userRepositoryImpl.create("user1");

        downloadTaskRepositoryImpl = new DownloadTaskRepositoryImpl(userRepositoryImpl);
        downloadTaskRepositoryImpl.create(user, DownloadTask.DownloadType.Followers, "tess__terr");  // THIS LINE GETS THE EXCEPTION
    }

Here is the relevant bit from DownloadTaskRepositoryImpl:

@Override
public DownloadTask create(User user, DownloadTask.DownloadType downloadType, String screenname) {
    DownloadTask downloadTask = new DownloadTask(downloadType, screenname);
    downloadTask.owner = Ref.create(user);
    save(downloadTask);
    return downloadTask;
}

private void save(DownloadTask downloadTask) {
    Closeable closeable = begin();
    ofy().save().entity(downloadTask);
    closeable.close();
}

Initially I thought that the problem was I hadn't set up the LocalServiceTestHelper. But I got the error still even after I added it.

Then I thought the problem was that I was using 100% eventual consistency but I set it to 0% eventual consistency and I still had the problem.

Then I thought maybe it was because I hadn't committed the transaction by calling closeable.close(); before I created the dependent object (i.e. DownloadTask). So I tried wrapping the creation of the object in its own transaction; that didn't work. I tried that PLUS reducing to 0% eventual consistency and that didn't work either.

For testing purposes, I can simply force the User.id to 1L and that satisfies Objecitfy, thus working around the problem.

But why wouldn't the @Id of User be set once I've saved the User?

** EDIT **

Here is my User class; note that the type of id is Long and not long:

import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;

@Entity
public class User {
    @Id public Long id;
    public String twitterScreenName;
    public String email;

    public User () {

    }

    public User (String twitterScreenName) {
        this.twitterScreenName = twitterScreenName;
    }
}
Horme answered 31/12, 2014 at 22:32 Comment(1)
Submitted as case 05482783 to Google Cloud Support.Horme
H
1

This is most likely caused by saving asynchronously instead of synchronously:

Be careful when saving entities with an autogenerated Long @Id field. A synchronous save() will populate the generated id value on the entity instance. An asynchronous save() will not; the operation will be pending until the async operation is completed.

Source: https://code.google.com/p/objectify-appengine/wiki/BasicOperations#Saving

It should fix the problem to change the save() method

From this:

private void save(DownloadTask downloadTask) {
    Closeable closeable = begin();
    ofy().save().entity(downloadTask);
    closeable.close();
}

To this:

private void save(DownloadTask downloadTask) {
    Closeable closeable = begin();
    ofy().save().entity(downloadTask).now();  // Added .now()
    closeable.close();
}

This fixed the problem for me in a similar situation where I was testing with Junit on a data model involving a linked object model.

Horme answered 19/2, 2015 at 20:3 Comment(0)
E
0

What's the type of 'id' in User class? If it's 'Long', keeping id=null should work. From what you explained, looks like it's 'long' (primitive). if that's the case, change it to 'Long' and try again.

Essary answered 31/12, 2014 at 23:53 Comment(1)
I'm using Long not long, sorry @apt_coder that I forgot to mention that. I've updated the question to show it.Horme

© 2022 - 2024 — McMap. All rights reserved.