Hot questions for Using Mockito in akka

Question:

I am using the akka framework with its Java API and mockito + Testkit for unit testing the actor

Here is the actor

public class K8sDeploymentCreator extends AbstractActor {
  private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);


  @Override
  public Receive createReceive() {
    return receiveBuilder().match(createK8sDeployment.class, msg -> {
      KubeNamespace kubenamespace = new KubeNamespace();
      KubeDeployment kubeDeployment = new KubeDeployment();
      Namespace namespace = kubenamespace.createNamespace(msg.kubeClient, msg.service);
      Deployment deployment = kubeDeployment.createDeployment(msg.service, msg.kubeClient, namespace);
      log.info("sending complete depl msg");

      getSender().tell(new K8sDeploymentComplete(deployment), getSelf());
    })
        .matchAny(o -> log.info("received unknown message")).build();
  }

}

And here is the test class

public class K8sDeploymentCreatorTest extends JUnitSuite {

  static ActorSystem system;


  @Before
  public  void setup() {
    system = ActorSystem.create();
    KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
    KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
    Deployment deployment = Mockito.mock(Deployment.class);
    Namespace namespace = Mockito.mock(Namespace.class);
    KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
    Service serviceTodeploy = new Service("group","artifact","version");
    DeployEnvironment deployEnvironment = new DeployEnvironment();
    deployEnvironment.setName("K8sDeploymentCreatorTest");
    serviceTodeploy.setDeployEnvironment(deployEnvironment);
    when(mockKubeNamespace.createNamespace(kubeClient, serviceTodeploy)).thenReturn(namespace);
    when(mockKubeDeployment.createDeployment(serviceTodeploy, kubeClient, namespace)).thenReturn(deployment);

  }


  @AfterClass
  public static void teardown() {
    TestKit.shutdownActorSystem(system);
    system = null;
  }

  @Test
  public void testK8sDeployment() {

    new TestKit(system) {
      {
        final Props props = Props.create(K8sDeploymentCreator.class);
        final ActorRef underTest = system.actorOf(props);
        KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
        KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
        Deployment deployment = Mockito.mock(Deployment.class);
        Namespace namespace = Mockito.mock(Namespace.class);
        KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
        DeployEnvironment deployEnvironment = new DeployEnvironment();
        deployEnvironment.setName("K8sDeploymentCreatorTest");
        Service serviceTodeploy = new Service("group","artifact","version");
        serviceTodeploy.setDeployEnvironment(deployEnvironment);

        createK8sDeployment msg = new createK8sDeployment(serviceTodeploy, kubeClient);
        underTest.tell(msg, getRef());
expectMsg(K8sDeploymentComplete)

      }
    };
  }

}

This fails with a NPE (NullPointerException) trying to execute code inside createNamespace(). This method has been mocked, should it skip the excution and just return whatever the when statement says it should return?

Is this because I am instantiating a new objec of KubeNamspace and also KubeDeployment where as the contact is for mocks?


Answer:

You are not actually mocking anything in your test. You are creating mock objects but they are not getting injected into the code under test. Your actor is executing the following code on response to a message:

KubeNamespace kubeNamespace = new KubeNamespace();
KubeDeployment kubeDeployment = new KubeDeployment();

This creates new un-mocked objects which will run their course as coded -- and often result in NPEs since they don't have the external dependencies they rely upon.

If you want to mock objects that are created this way you either have to refactor your code to extract the creation of them into a mockable factory class or use a more invasive mock library such as PowerMock or jMockit.

Example of Factory mock
class KubeFactory {
    public KubeNamespace makeNamespace() {
        return new KubeNamespace();
    }
    public KubeDeployment makeDeployment() {
        return new KubeDeployment();
    }
}

public class K8sDeploymentCreator extends AbstractActor {

    private final KubeFactory factory;

    K8sDeploymentCreator() {
        this(new KubeFactory());
    }

    // This constructor allows you to override the factory used for testing
    K8sDeploymentCreator(KubeFactory factory) {
        this.factory = factory;
    }
    @Override
    public Receive createReceive() {
      return receiveBuilder().match(createK8sDeployment.class, msg -> {
          KubeNamespace kubenamespace = factory.makeNamespace();
          KubeDeployment kubeDeployment = factory.makeDeployment();
          // rest is as before...
        });
    }
}

Then in your test class you create a test KubeFactory which returns mocked instances for the classes you are testing with:

@Test
public void testK8sDeployment() {

  new TestKit(system) {
    {
      final KubeFactory mockFactory = mock(KubeFactory.class);
      when(mockFactory.makeNamespace()).thenReturn(mockKubeNamespace);
      when(mockFactory.makeDeployment()).thenReturn(mockKubeDeployment);
      final Props props = Props.create(K8sDeploymentCreator.class, mockFactory);
      final ActorRef underTest = system.actorOf(props);
      // and so on...
    }
  }
}