Sling模型单元测试:在SlingHttpServletRequest

时间:2017-10-28 18:49:50

标签: mockito aem sling sling-models

我在测试Sling模型时遇到了一些麻烦:currentPage因某些原因没有被注入。

My 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$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.rules.ExternalResource$1.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$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.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为null所以我在这里仍然遗漏了一些东西。

更新2

我将代码推送到Github,您可以在以下URL中找到该项目 https://github.com/josebercianowhitbread/myproject

注意:

- 在AEM 6.3中进行了测试

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

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

- Sling模型功能非常简单:它在内容树上升,直到找到带有" isRootPage"的父节点。属性设置为true。

您可能有任何问题让我知道。

提前感谢您提供的任何帮助。

更新3

Justin Edelson善意地纠正并提供了测试代码。非常感谢他和Ahmed Musallam一直追逐这个帖子,直到他确保一切正常:)

我的初始代码的两个主要问题是: 我试图模拟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);
   }

}

2 个答案:

答案 0 :(得分:3)

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

您需要注册注入器和注释进程:

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

  

注意:注册这些服务时,请确保注册服务的实际实例,并模拟实例。您需要真正的impl才能正确进行吊索模型注入:

     

Page      aemObjectInjector = new AemObjectInjector()

<强> ============================================ ====

<强>更新

在查看了您提供的简单仓库here后,我看了一下并修复了测试,使其工作就像您希望它在您的仓库的叉子中here

为了其他人,这里是:模型类,测试类和json资源:

<强> MyModel.java:

context.registerService(AemObjectInjector.class, aemObjectInjector);

以下是测试类: MyModelTest.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();
    }

}

json资源如下:

<强> MyModel.json

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

<强>共page.json

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

答案 1 :(得分:1)

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

Documentation link