web-dev-qa-db-ja.com

テスト中に@Autowiredプライベートフィールドを挿入する

基本的にアプリケーションのランチャーであるコンポーネントのセットアップがあります。次のように構成されます。

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyServiceには@Service Springアノテーションが付けられており、問題なくランチャークラスに自動接続されています。

MyLauncherのjUnitテストケースをいくつか作成したいので、次のようなクラスを開始しました。

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

MyServiceのMockオブジェクトを作成し、テストクラスのmyLauncherに挿入できますか?現在、Springが自動配線を処理しているため、myLauncherにゲッターまたはセッターはありません。可能であれば、ゲッターとセッターを追加する必要はありません。 @Before initメソッドを使用して、自動配線された変数にモックオブジェクトを挿入するようにテストケースに指示できますか?

これについて完全に間違っている場合は、お気軽にそれを言ってください。私はまだこれが初めてです。私の主な目標は、セッターメソッドを記述したり@Autowiredファイルを使用したりせずに、そのapplicationContext-test.xml変数にモックオブジェクトを配置するJavaコードまたは注釈を作成することです。 。テストのためだけに個別のアプリケーションコンテンツを維持するのではなく、.Javaファイルでテストケースのすべてを維持したいです。

モックオブジェクトにMockitoを使用したいと考えています。過去にorg.mockito.Mockitoを使用し、Mockito.mock(MyClass.class)でオブジェクトを作成することでこれを実行しました。

61
Kyle

テストでMyLauncherにモックを絶対に注入できます。あなたが誰かを使用しているモックフレームワークを示したら、答えをすぐに提供できると確信しています。 mockitoを使用して、@ RunWith(MockitoJUnitRunner.class)の使用とmyLauncherの注釈の使用を検討します。以下のようになります。

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}
70
Manuel Quinones

受け入れられた答え(MockitoJUnitRunner@InjectMocksを使用)は素晴らしいです。しかし、少しの軽量化(特別なJUnitランナーなし)、特に不定期の使用のために「魔法的」ではない(より透明な)ものが必要な場合は、イントロスペクションを使用してプライベートフィールドを直接設定できます。

Springを使用している場合は、このためのユーティリティクラスが既にあります。 org.springframework.test.util.ReflectionTestUtils

使い方はとても簡単です:

ReflectionTestUtils.setField(myLauncher, "myService", myService);

最初の引数はターゲットBean、2番目は(通常はプライベート)フィールドの名前、最後は注入する値です。

Springを使用しない場合、このようなユーティリティメソッドを実装するのは非常に簡単です。このSpringクラスを見つける前に使用したコードは次のとおりです。

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
35
Pierre Henry

場合によっては、@Componentをリファクタリングして、コンストラクターまたはセッターベースのインジェクションを使用してテストケースをセットアップできます(@Autowiredに依存できます)。これで、代わりにテストスタブを実装することにより、モックフレームワークなしでテストを完全に作成できます(例:Martin Fowlerの MailServiceStub ):

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

この手法は、テストとテスト対象のクラスが同じパッケージにある場合に特に役立ちます。デフォルトの package-private アクセス修飾子を使用して、他のクラスがアクセスできないようにするためです。 src/main/Javaには本番コードを保持できますが、src/main/testディレクトリにはテストを保持できます。


Mockitoが好きなら、 MockitoJUnitRunner に感謝します。 @Manuelが示したような「魔法の」ことをすることができます:

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

または、デフォルトのJUnitランナーを使用して、setUp()メソッドで MockitoAnnotations.initMocks() を呼び出して、Mockitoに注釈付きの値を初期化させることができます。詳細については、javadocの @ InitMocks と、私が書いた ブログ投稿 を参照してください。

14
matsev

MyLauncherクラスで自動配線を機能させるには(myServiceの場合)、myLauncherを自動配線して、コンストラクターを呼び出すのではなく、Springで初期化する必要があります。それが自動配線されると(そしてmyServiceも自動配線されます)、Spring(1.4.0以降)は、テストに配置できる@MockBeanアノテーションを提供します。これにより、コンテキスト内の一致する単一のBeanがそのタイプのモックに置き換えられます。その後、@ Beforeメソッドで、必要なモックをさらに定義できます。

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

これで、MyLauncherクラスは変更されないままになり、MyService Beanは、定義したとおりにメソッドが値を返すモックになります。

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

言及された他の方法に対するこの利点のいくつかは、次のとおりです。

  1. MyServiceを手動で注入する必要はありません。
  2. Mockitoランナーまたはルールを使用する必要はありません。
1
snydergd

これを見てください link

次に、テストケースを

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}

私はSpringの新しいユーザーです。これに対して別の解決策を見つけました。リフレクションを使用して必要なフィールドを公開し、モックオブジェクトを割り当てます。

これは私の認証コントローラーであり、いくつかのAutowiredプライベートプロパティがあります。

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullanıcıadı veya şifre hatalı");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

そして、これはAuthControllerの私のJunitテストです。私はパブリックに必要なプライベートプロパティを作成し、モックオブジェクトをそれらに割り当て、ロックします:)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

この方法の長所と短所はわかりませんが、これは機能しています。このテクニックにはもう少しコードがありますが、これらのコードはさまざまな方法などで分離できます。この質問にはさらに良い答えがありますが、別の解決策を示したいと思います。悪い英語でごめんなさい。みんなに良いJavaを持っている:)

0
kodmanyagha