使用Dagger和AWS Lambda进行单元测试

问题描述 投票:2回答:1

我正在我的Dagger项目中设置Serverless,该项目具有许多Java AWS Lambda实现。 Lambda代码相对简单,主要处理读取请求和写入响应,而服务类则承担大部分繁重的任务。

我想对Lambda代码进行单元测试,但是为此设置Dagger遇到了一些麻烦。我遇到的特定问题是,我依赖于环境变量来指定AWS区域来构建DynamoDB客户端。运行单元测试时,此环境变量为null,从而导致构建器引发异常。这是完整的异常(希望下面的代码有一定意义):

org.mockito.exceptions.base.MockitoException: 
Cannot instantiate @InjectMocks field named 'handler' of type 'class com.mealplanner.function.ListMealsHandler'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : Could not find region information for 'null' in SDK metadata.

    at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:165)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachCallbacks$0(TestMethodTestDescriptor.java:129)
    at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:155)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachCallbacks(TestMethodTestDescriptor.java:128)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:107)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:113)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:121)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:121)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.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:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
    Suppressed: java.lang.NullPointerException
        at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:211)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterEachCallbacks$11(TestMethodTestDescriptor.java:217)
        at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13(TestMethodTestDescriptor.java:229)
        at java.util.ArrayList.forEach(ArrayList.java:1249)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks(TestMethodTestDescriptor.java:227)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterEachCallbacks(TestMethodTestDescriptor.java:216)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:119)
        ... 46 more
Caused by: com.amazonaws.SdkClientException: Could not find region information for 'null' in SDK metadata.
    at com.amazonaws.client.builder.AwsClientBuilder.getRegionObject(AwsClientBuilder.java:251)
    at com.amazonaws.client.builder.AwsClientBuilder.withRegion(AwsClientBuilder.java:238)
    at com.mealplanner.config.AppModule.providesAmazonDynamoDB(AppModule.java:17)
    at com.mealplanner.config.AppModule_ProvidesAmazonDynamoDBFactory.proxyProvidesAmazonDynamoDB(AppModule_ProvidesAmazonDynamoDBFactory.java:34)
    at com.mealplanner.config.AppModule_ProvidesAmazonDynamoDBFactory.provideInstance(AppModule_ProvidesAmazonDynamoDBFactory.java:25)
    at com.mealplanner.config.AppModule_ProvidesAmazonDynamoDBFactory.get(AppModule_ProvidesAmazonDynamoDBFactory.java:21)
    at com.mealplanner.config.AppModule_ProvidesAmazonDynamoDBFactory.get(AppModule_ProvidesAmazonDynamoDBFactory.java:1)
    at com.mealplanner.dal.DynamoDbAdapter_Factory.provideInstance(DynamoDbAdapter_Factory.java:25)
    at com.mealplanner.dal.DynamoDbAdapter_Factory.get(DynamoDbAdapter_Factory.java:21)
    at com.mealplanner.dal.DynamoDbAdapter_Factory.get(DynamoDbAdapter_Factory.java:1)
    at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
    at com.mealplanner.dal.MealRepository_Factory.provideInstance(MealRepository_Factory.java:24)
    at com.mealplanner.dal.MealRepository_Factory.get(MealRepository_Factory.java:20)
    at com.mealplanner.dal.MealRepository_Factory.get(MealRepository_Factory.java:1)
    at dagger.internal.DoubleCheck.get(DoubleCheck.java:47)
    at com.mealplanner.config.DaggerAppComponent.getMealRepository(DaggerAppComponent.java:52)
    at com.mealplanner.function.ListMealsHandler.<init>(ListMealsHandler.java:31)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.mockito.internal.util.reflection.FieldInitializer$NoArgConstructorInstantiator.instantiate(FieldInitializer.java:195)
    at org.mockito.internal.util.reflection.FieldInitializer.acquireFieldInstance(FieldInitializer.java:137)
    at org.mockito.internal.util.reflection.FieldInitializer.initialize(FieldInitializer.java:91)
    at org.mockito.internal.configuration.injection.PropertyAndSetterInjection.initializeInjectMocksField(PropertyAndSetterInjection.java:94)
    at org.mockito.internal.configuration.injection.PropertyAndSetterInjection.processInjection(PropertyAndSetterInjection.java:79)
    at org.mockito.internal.configuration.injection.MockInjectionStrategy.process(MockInjectionStrategy.java:68)
    at org.mockito.internal.configuration.injection.MockInjectionStrategy.relayProcessToNextStrategy(MockInjectionStrategy.java:89)
    at org.mockito.internal.configuration.injection.MockInjectionStrategy.process(MockInjectionStrategy.java:71)
    at org.mockito.internal.configuration.injection.MockInjectionStrategy.relayProcessToNextStrategy(MockInjectionStrategy.java:89)
    at org.mockito.internal.configuration.injection.MockInjectionStrategy.process(MockInjectionStrategy.java:71)
    at org.mockito.internal.configuration.injection.MockInjection$OngoingMockInjection.apply(MockInjection.java:92)
    at org.mockito.internal.configuration.DefaultInjectionEngine.injectMocksOnFields(DefaultInjectionEngine.java:25)
    at org.mockito.internal.configuration.InjectingAnnotationEngine.injectMocks(InjectingAnnotationEngine.java:87)
    at org.mockito.internal.configuration.InjectingAnnotationEngine.processInjectMocks(InjectingAnnotationEngine.java:48)
    at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:42)
    at org.mockito.MockitoAnnotations.initMocks(MockitoAnnotations.java:69)
    at org.mockito.internal.framework.DefaultMockitoSession.<init>(DefaultMockitoSession.java:36)
    at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:78)
    ... 52 more

我看过DaggerMock为我的对象提供模拟,但是我无法使其正常工作。

以下是设置Dagger和其他相关类的代码段:

AppModule.java

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import dagger.Module;
import dagger.Provides;

@Module
public class AppModule {

    private static final String AWS_REGION = System.getenv("region");

    @Provides
    public AmazonDynamoDB providesAmazonDynamoDB() {
        return AmazonDynamoDBClientBuilder.standard()
                .withRegion(AWS_REGION)
                .build();
    }
}

AppComponent.java

import javax.inject.Singleton;

import com.mealplanner.dal.DynamoDbAdapter;
import com.mealplanner.dal.MealRepository;

import dagger.Component;

@Singleton
@Component(modules = { AppModule.class })
public interface AppComponent {

    DynamoDbAdapter getDynamoDbAdapter();

    MealRepository getMealRepository();
}

DynamoDbAdapter.java

import javax.inject.Inject;
import javax.inject.Singleton;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;

@Singleton
public class DynamoDbAdapter {

    private final AmazonDynamoDB client;

    @Inject
    public DynamoDbAdapter(final AmazonDynamoDB client) {
        this.client = client;
    }

    public DynamoDBMapper createDbMapper(final DynamoDBMapperConfig mapperConfig) {
        return new DynamoDBMapper(client, mapperConfig);
    }
}

MealRepository.java

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Singleton;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedQueryList;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.mealplanner.domain.Meal;

@Singleton
public class MealRepository {

    private static final String MEALS_TABLE_NAME = System.getenv("tableName");

    private final DynamoDBMapper mapper;

    @Inject
    public MealRepository(final DynamoDbAdapter dynamoDbAdapter) {
        final DynamoDBMapperConfig mapperConfig = DynamoDBMapperConfig.builder()
                .withTableNameOverride(new DynamoDBMapperConfig.TableNameOverride(MEALS_TABLE_NAME))
                .build();

        this.mapper = dynamoDbAdapter.createDbMapper(mapperConfig);
    }
}

ListMealsHandler.java

请参阅下面的代码片段注释以获取一些说明。

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.mealplanner.config.AppComponent;
import com.mealplanner.config.DaggerAppComponent;
import com.mealplanner.dal.MealRepository;
import com.mealplanner.domain.Meal;
import com.mealplanner.function.util.ApiGatewayRequest;
import com.serverless.ApiGatewayResponse;

public class ListMealsHandler implements RequestHandler<ApiGatewayRequest, ApiGatewayResponse> {

    @Inject
    MealRepository repository;

    public ListMealsHandler() {
        final AppComponent component = DaggerAppComponent.builder().build();
        this.repository = component.getMealRepository();
    }

    @Override
    public ApiGatewayResponse handleRequest(final ApiGatewayRequest request, final Context context) {
        //read from request, call repository, and build response
    }
}
  • 我已经为repository包括了一个可注入字段,纯粹用于测试,理想情况下,我会将依赖项注入到构造函数中
  • 我在构造函数中创建了AppComponent,因为这似乎是Lambda的“入口”点。我了解您通常会在onCreate方法或main方法中进行设置,但我认为我无法访问Lambda。

  • 这就是我想测试Lambda函数的方式,即模拟依赖关系并基于此执行断言。

ListMealsHandlerTest.java

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.List;

import org.junit.Rule;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.amazonaws.services.lambda.runtime.Context;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mealplanner.config.AppComponent;
import com.mealplanner.config.AppModule;
import com.mealplanner.dal.MealRepository;
import com.mealplanner.domain.Meal;
import com.mealplanner.function.ListMealsHandler;
import com.mealplanner.function.util.ApiGatewayRequest;
import com.mealplanner.function.util.Identity;
import com.mealplanner.function.util.RequestContext;
import com.serverless.ApiGatewayResponse;

@ExtendWith(MockitoExtension.class)
public class ListMealsHandlerTest {

    private static final String USER_ID = "user1";

    @Mock
    private MealRepository mealRepository;

    @Mock
    private ApiGatewayRequest request;

    @Mock
    private RequestContext requestContext;

    @Mock
    private Identity identity;

    @Mock
    private Context context;

    @InjectMocks
    private ListMealsHandler handler;

    @Test
    public void all_users_meals_are_returned() throws Exception {
        final List<Meal> meals = Arrays.asList();
        when(mealRepository.getAllMealsForUser(USER_ID)).thenReturn(meals);

        when(request.getRequestContext()).thenReturn(requestContext);
        when(requestContext.getIdentity()).thenReturn(identity);
        when(identity.getCognitoIdentityId()).thenReturn(USER_ID);

        final ApiGatewayResponse response = handler.handleRequest(request, context);
        final ObjectMapper objectMapper = new ObjectMapper();
        final List<Meal> actualMeals = objectMapper.readValue(response.getBody(), new TypeReference<List<Meal>>() {
        });

        assertThat(actualMeals).containsExactlyInAnyOrderElementsOf(meals);
    }
}

问题

  1. 我是否正确设置了匕首?
  2. 如果没有,我应该如何设置?
  3. 在我的Lambda函数的构造函数中构建AppComponent是否正确?
  4. 是否可以像我想要的那样进行单元测试?
  5. 如果是这样,我应该如何为该测试设置Dagger?

我正在尝试在我的无服务器项目中设置Dagger,该项目具有许多Java AWS Lambda实现。 Lambda代码相对简单,主要处理读取请求和...

java testing aws-lambda dagger-2 daggermock
1个回答
0
投票

我遇到同样的问题,请提出克服的最佳方法。

© www.soinside.com 2019 - 2024. All rights reserved.