Search code examples
eclipse-scout

Eclipse Scout Neon mock backend service


In our project I have modules scout.client, scout.server, scout.shared and backend.

Backend has no dependencies to scout.server and scout.shared, but scout.server has dependencies to backend.

enter image description here

Inside backend project I have all business logic and calling all outside services.

My problem is when I try to test scout services that use some service from backend.

Because scout provide some great tool for mocking beans, we defined our service inside backend as beans as :

 BEANS.getBeanManager().registerClass(CarService.class);
 BEANS.getBeanManager().registerClass(PartnerService.class);

Both, CarService.class and PartnerService.class are in backend.

When I try to write some tests and I add @BeanMock to service in test

@BeanMock
private IPartnerService partnerService;

I get mock, but then every return every function is null, even if I write

doReturn(PartnerBuilder.standardPartnerListWithOneElement()).when(this.partnerService)
    .getPartners(any(Set.class));

If I debug in my test, before this test is called with debugger I can get :

  partnerService.getPartners(...) -> return a list of person 

what is right, but when class that is tested calles this service it return null.

I understand that this could be due to missing annotation on interface @ApplicationScoped. Without this there is no guarantee that only one bean is created, and when statement react on another copy of that bean...?

I could not add annotation on interface because backend has no dependencies to scout modules.

How could I handle this kind of cases?


Tested class is :

 public class UtilityPartner {

  /**
   * Method return service bean for getting partners by ids.
   *
   * @return
   */
   private static IPartnerService getPartnerService() {

    return BEANS.get(IPartnerService.class);
   }

  public static String getPartnerName(final Long partnerId) {

    if (partnerId == null) {
      return "";
    }

    final List<Partner> partners =
        (List<Partner>) getPartnerService().getPartners(Sets.newHashSet(partnerId));
    if (partners == null || partners.isEmpty()) {
      return "";
    }
    final Partner partner = partners.get(0);
    return LookupUtil.createLookupDescription(partner.getId(), partner.getName());
  }

}

test class is :

 @RunWith(ServerTestRunner.class)
 @RunWithSubject("anonymous")
 @RunWithServerSession(ServerSession.class)
 public class TestUtilityPartner {

    @BeanMock
    private IPartnerService partnerService;

     @Before
     public void init() {
         doReturn(PartnerBuilder.standardPartnerListWithOneElement()).when(this.partnerService).getPartners(any(Set.class));

     }

     @Test
     public void getPartnerName() {

        final String name = UtilityPartner.getPartnerName(10L);
        Assert.assertEquals("My name", name); // NAME IS ""
     }
}

Solution

  • I think that you should register your mock instance in the Bean manager (See bean registration in the Scout Architecture Document). You should use a small order (-10 000 is recommended for tests), in order for your mock to win over the productive registration. The best approach is to use the TestingUtility class to register/unregister your mock. Do not forget to call the unregisterBean() method (in the method annotated with @After):

    import java.util.Collections;
    
    import org.eclipse.scout.rt.platform.BeanMetaData;
    import org.eclipse.scout.rt.platform.IBean;
    import org.eclipse.scout.rt.testing.shared.TestingUtility;
    import org.junit.After;
    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;
    import org.mockito.Mockito;
    
    public class TestUtilityPartner {
    
        private IBean<?> beanRegistration;
    
        @Before
        public void init() {
            partnerService = Mockito.mock(IPartnerService.class);
    
            // Register the mock using the Bean meta information:
            BeanMetaData beanData = new BeanMetaData(IPartnerService.class)
               .withInitialInstance(partnerService)
               .withApplicationScoped(true);
            this.beanRegistration = TestingUtility.registerBean(beanData);
    
    
           // Mockito behavior:
           Mockito.doReturn(Collections.singletonList(new Partner(34L, "John Smith")))
               .when(partnerService).getPartners(Mockito.any(Set.class));
        }
    
        @After
        public void after() {
            // Unregister the mocked services:
            TestingUtility.unregisterBean(this.beanRegistration);
        }
    
        @Test
        public void getPartnerName() {
            String name = UtilityPartner.getPartnerName(10L);
            Assert.assertEquals("10 - John Smith", name);
        }
    }
    

    I am not sure what @BeanMock (org.eclipse.scout.rt.testing.platform.mock.BeanMock) is doing, but according to Judith Gull's answer it will not work:

    Using @BeanMock does not help here, because you are not using an application scoped service:

    In the init method you are changing the local field partnerService. However, in your test you call UtilityPartner.getPartnerService, which is creating a new instance (with BEANS.get(IPartnerService.class)).

    @BeanMock is more useful for convenience for mocking application scoped beans.