Sling 模型单元测试:在 SlingHttpServletRequest 中模拟 currentPage

Sling model unit tests: mock currentPage in SlingHttpServletRequest

我在测试 Sling 模型时遇到了一些问题:currentPage 由于某种原因没有被注入。

我的 Sling 模型如下所示:

@Model( adaptables = { SlingHttpServletRequest.class, Resource.class }, 
    resourceType = MyModel.RESOURCE_TYPE)
public class MyModel {

   public static final String RESOURCE_TYPE = "myproject/components/renderer";

   @Inject
   private Page currentPage;

   // Model methods, etc.

}

我为它写了一些 JUnit 测试,像这样:

@RunWith(MockitoJUnitRunner.class)
public class MyModelTest {

  @Rule
  public final AemContext context = new AemContext();

  @Mock
  private SlingHttpServletRequest request;

  private static final String RESOURCE_PATH = "/content/myproject/jcr:content/myModel";
  private static final String PAGE_PATH = "/content/common/page";

  private MyModel myModel;

  @Before
  public final void setUp() throws Exception {
    context.load().json("/models/MyModel.json",RESOURCE_PATH);
    context.load().json("/common-page.json", PAGE_PATH);

    Resource pageResource = context.resourceResolver().getResource(PAGE_PATH);
    Page page = pageResource.adaptTo(Page.class);

    context.currentPage(page);
    context.addModelsForClasses(MyModel.class);
    when(request.getResource()).thenReturn(context.resourceResolver().getResource(RESOURCE_PATH));
    myModel = request.getResource().adaptTo(MyModel.class);
  }

  @Test
  public void simpleLoadTest(){
    assertNotNull(myModel);
  }   
}

这是我得到的错误:

   [main] WARN org.apache.sling.models.impl.ModelAdapterFactory - Could not adapt to model
  org.apache.sling.models.factory.MissingElementsException: Could not inject all required fields into class com.myproject.common.core.models.MyModel
   at org.apache.sling.models.impl.ModelAdapterFactory.createObject(ModelAdapterFactory.java:558)
   at org.apache.sling.models.impl.ModelAdapterFactory.internalCreateModel(ModelAdapterFactory.java:319)
   at org.apache.sling.models.impl.ModelAdapterFactory.getAdapter(ModelAdapterFactory.java:195)
   at org.apache.sling.testing.mock.sling.MockAdapterManagerImpl.getAdapter(MockAdapterManagerImpl.java:146)
   at org.apache.sling.testing.mock.sling.ThreadsafeMockAdapterManagerWrapper.getAdapter(ThreadsafeMockAdapterManagerWrapper.java:46)
   at org.apache.sling.api.adapter.SlingAdaptable.adaptTo(SlingAdaptable.java:104)
   at org.apache.sling.testing.resourceresolver.MockResource.adaptTo(MockResource.java:110)
   at uk.co.restaurants.common.core.models.MyModelTest.setUp(MyModelTest.java:44)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:498)
   at org.junit.runners.model.FrameworkMethod.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.rules.ExternalResource.evaluate(ExternalResource.java:48)
   at org.junit.rules.RunRules.evaluate(RunRules.java:20)
   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.run(ParentRunner.java:238)
   at org.junit.runners.ParentRunner.schedule(ParentRunner.java:63)
   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
   at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:53)
   at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:229)
   at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
   at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
   at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
   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:678)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
   Suppressed: org.apache.sling.models.factory.MissingElementException: Could not inject private com.day.cq.wcm.api.Page com.myproject.common.core.models.MyModel.currentPage
       at org.apache.sling.models.impl.ModelAdapterFactory.createObject(ModelAdapterFactory.java:562)
       ... 34 more
   Caused by: org.apache.sling.models.factory.ModelClassException: No injector returned a non-null value!
       at org.apache.sling.models.impl.ModelAdapterFactory.injectElement(ModelAdapterFactory.java:482)
       at org.apache.sling.models.impl.ModelAdapterFactory.createObject(ModelAdapterFactory.java:560)
       ... 34 more

对于其他一些 Sling 模型测试,注入工作得很好,尽管对于 currentPage 我不确定如何进行。 我找不到关于在 Sling 模型中模拟 currentPage 对象的文档。

如有任何帮助,我们将不胜感激。


更新

以下评论有助于更好地理解此测试的外观。 我做了一些更改,但我的测试仍然失败。现在 类 看起来像这样:

@RunWith(MockitoJUnitRunner.class)
public class MyModelTest {

  @Rule
  public final AemContext context = new AemContext();

  @Mock
  private SlingHttpServletRequest request;

  @Mock
  AemObjectAnnotationProcessorFactory factory;

  @InjectMocks
  AemObjectInjector aemObjectInjector;

  private static final String RESOURCE_PATH = "/content/myproject/jcr:content/mymodel";
  private static final String PAGE_PATH = "/content/common/page";

  private MyModel mymodel;

  @Before
  public final void setUp() throws Exception {
    context.load().json("/common-page.json", PAGE_PATH);
    Resource pageResource = context.resourceResolver().getResource(PAGE_PATH);
    Page page = pageResource.adaptTo(Page.class);
    context.currentPage(page);

    context.load().json("/models/MyModel.json",RESOURCE_PATH);
    context.request().setServletPath(RESOURCE_PATH);
    context.registerInjectActivateService(factory);
    context.registerService(AemObjectInjector.class, aemObjectInjector);            

    Mockito.when(request.getResource())
      .thenReturn(context.resourceResolver().getResource(RESOURCE_PATH));
    Resource resource = request.getResource();
    mymodel = resource.adaptTo(MyModel.class);
  }

  @Test
  public void simpleLoadTest(){
      assertNotNull(mymodel);
  }  

}

以及带有特定喷油器的更新模型:

@Model(
   adaptables = { SlingHttpServletRequest.class }, 
   resourceType = MyModel.RESOURCE_TYPE)
public class MyModel {

  public static final String RESOURCE_TYPE = "myproject/components/renderer";

  @AemObject
  private Page currentPage;

  // Model methods, etc.

}

setUp() 方法没有抛出任何异常,也没有任何警告。 变量 mymodel 为空,所以我仍然遗漏了一些东西。


更新 2

我把代码推送到了Github,你可以在下面找到项目URL https://github.com/josebercianowhitbread/myproject

备注:

-已在 AEM 6.3 中测试

-像往常一样部署项目:mvn clean install -PautoInstallPackage

-该项目添加了一些示例页面以确保 Sling 模型按预期工作

-Sling 模型功能非常简单:它沿着内容树向上移动,直到找到 "isRootPage" 属性 设置为 true 的父节点。

如有任何问题,请告诉我。

在此先感谢您提供的任何帮助。


更新 3

Justin Edelson 好心纠正并提供了测试代码。非常感谢他和 Ahmed Musallam,他一直在努力 post 直到他确保一切正常 :)

我的初始代码的两个主要问题是: 我试图模拟 Slick 请求,但应该使用来自 AemContext 的请求。 该型号未注册。

public class MyModelTest {

   @Rule
   public final AemContext context = new AemContext();

   private MockSlingHttpServletRequest request;

   AemObjectAnnotationProcessorFactory factory = new AemObjectAnnotationProcessorFactory();

   AemObjectInjector aemObjectInjector = new AemObjectInjector();

   private static final String RESOURCE_PATH = "/content/parent-page/jcr:content/content/renderer";
   private static final String PAGE_PATH = "/content/parent-page";

   private MyModel mymodel;

   @Before
   public final void setUp() throws Exception {
       request = context.request();
       context.addModelsForClasses(MyModel.class);
       context.load().json("/pages/common-page.json", PAGE_PATH);
       Resource pageResource = 
       context.resourceResolver().getResource(PAGE_PATH);

       Page page = pageResource.adaptTo(Page.class);
       context.currentPage(page);

       context.load().json("/models/MyModel.json", RESOURCE_PATH);
       context.registerInjectActivateService(factory);
       context.registerService(AemObjectInjector.class, aemObjectInjector);


       request.setResource(context.resourceResolver()
         .getResource(RESOURCE_PATH));
       mymodel = request.adaptTo(MyModel.class);
   }

   @Test
   public void simpleLoadTest() {
       assertNotNull(mymodel);
   }

}

在模型中使用 @AemObject(来自 com.adobe.acs.commons.models.injectors.annotation.AemObject)而不是 @Inject 注释,以便成功注入当前页面。

Documentation link

您依赖 ACS 的 @AemObject 喷油器。请记住,注入器和任何 sling 注入器都是 OSGI 服务,并且您的 AEM 上下文没有注册该服务,即:它不知道 AemObjectInjector,这就是为什么您永远不会获得非空值的原因 Page.

您需要注册注入器和注释处理器:

要注册服务,请查看 wcm.io 的文档:Registering OSGi service

Note: When registering those services, make sure you register a real instance of the services and not a mocked instance. You need the real impl for the sling model injection to occur properly:

aemObjectInjector = new AemObjectInjector() context.registerService(AemObjectInjector.class, aemObjectInjector);

======================================== ========

更新:

查看您提供的简单存储库后 here I have taken a look and fixed the test to make it work like you want it to in a fork of your repo here

为了其他人,这里有:模型 class、测试 class 和 json 资源:

MyModel.java:

package com.myproject.models;

import javax.annotation.PostConstruct;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;

import com.adobe.acs.commons.models.injectors.annotation.AemObject;
import com.day.cq.wcm.api.Page;

@Model(
    adaptables = { SlingHttpServletRequest.class },
    resourceType = MyModel.RESOURCE_TYPE)
public class MyModel {

    public static final String RESOURCE_TYPE = "myproject/components/renderer";

    @AemObject
    private Page currentPage;

    protected final String ROOT_PAGE_PROPERTY = "isRootPage";
    private Page rootPage;

    @PostConstruct
    private void initModel() {
        // Fetches the root language page in order to get the data from that node.
        while (!isRootPage(currentPage)) {
            currentPage = currentPage.getParent();
        }
        rootPage = currentPage;
    }

    private boolean isRootPage(Page selectedPage) {
        return selectedPage.getProperties().get(ROOT_PAGE_PROPERTY, false);
    }

    public String getRootPath() {
            return rootPage.getPath();
    }

}

这是测试class:MyModelTest.java

package com.myproject.models;

import static org.junit.Assert.*;

import org.apache.sling.api.SlingHttpServletRequest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import com.adobe.acs.commons.models.injectors.annotation.impl.AemObjectAnnotationProcessorFactory;
import com.adobe.acs.commons.models.injectors.impl.AemObjectInjector;

import io.wcm.testing.mock.aem.junit.AemContext;


@RunWith(MockitoJUnitRunner.class)
public class MyModelTest {

    @Rule
    public final AemContext context = new AemContext();

    @Mock
    private SlingHttpServletRequest request;


    private static final String RESOURCE_PATH = "/content/parent-page/jcr:content/content/renderer";
    private static final String PAGE_PATH = "/content/parent-page";

    private MyModel mymodel;
    private AemObjectInjector aemObjectInjector;
    private AemObjectAnnotationProcessorFactory factory;

    @Before
    public final void setUp() throws Exception {

        // register model
        // NOTE: this is the alternative to creating an adapter/adapter factory.
        context.addModelsForClasses(MyModel.class);

        // load page and resource from json
        context.load().json("/pages/common-page.json", PAGE_PATH);
        context.load().json("/models/MyModel.json", RESOURCE_PATH);

        // set current page to the page path
        context.currentPage(PAGE_PATH);

        // register ACS AemObjectInjector service
        aemObjectInjector = new AemObjectInjector();
        context.registerService(AemObjectInjector.class, aemObjectInjector);

        // adapt request to model
        mymodel = context.request().adaptTo(MyModel.class);
    }

    @Test
    public void simpleLoadTest() {
        // mymodel is NOT null
        assertNotNull(mymodel);
        // mymodel's page has property 'isRootPage=true', therefor it's the root page
        assertEquals(mymodel.getRootPath(), PAGE_PATH);
    }
}

json资源如下:

MyModel.json

{
    "jcr:primaryType": "nt:unstructured",
    "sling:resourceType": "myproject/components/renderer"
}

普通-page.json

{
    "jcr:primaryType": "cq:Page",
    "jcr:createdBy": "admin",
    "jcr:created": "Fri Nov 03 2017 13:56:12 GMT+0000",
    "jcr:content":
    {
        "jcr:primaryType": "cq:PageContent",
        "jcr:createdBy": "admin",
        "jcr:title": "Parent page",
        "cq:template": "/apps/myproject/templates/common-page",
        "isRootPage": true,
        "jcr:created": "Fri Nov 03 2017 13:56:12 GMT+0000",
        "cq:lastModified": "Fri Nov 03 2017 13:56:12 GMT+0000",
        "sling:resourceType": "myproject/components/page",
        "cq:lastModifiedBy": "admin"
    }
}