読者です 読者をやめる 読者になる 読者になる

m_shige1979のささやかな抵抗と欲望の日々

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

なんとなく作ったサイト

http://www.it-check-matome.info/


Github(注意すること)

https://github.com/mshige1979

AWSのSESで送信者名を指定する

メール送信で日本語を使用したい

文字化け起こりましたのでネットを使用して調べました

以下の対応でなんとかなりました。m(_ _)m

実装

import java.io.UnsupportedEncodingException;

import javax.mail.internet.InternetAddress;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;

public class Sample01 {
	
	private static final String ACCESS_KEY = "アクセスキー";
	private static final String SECRET_ACCESS_KEY = "シークレットアクセスキー";
	
	public static void main(String[] args) {
		
		String TO = "送信先のメールアドレス";
		String FROM = "送信元のメールアドレス";
		String SUBJECT = "テストメール";
		String BODY = "テストメールです。";
		
		// 宛先
		Destination destination = new Destination().withToAddresses(new String[]{TO});
	    // 件名
	    Content subject = new Content().withData(SUBJECT);
		// 本文
	    Body body = new Body().withText(new Content().withData(BODY));
	    
	    // メッセージ
	    Message message = new Message()
	        .withSubject(subject)
	        .withBody(body);
	    
	    // リクエストを作成
	    SendEmailRequest request = new SendEmailRequest()
	        .withSource(senderAddressBuilder(FROM,"送信者です"))
	        .withDestination(destination)
	        .withMessage(message);

	    // 認証
	    AmazonSimpleEmailServiceClient client = new AmazonSimpleEmailServiceClient(
	    		new  BasicAWSCredentials(ACCESS_KEY, SECRET_ACCESS_KEY));
	    
	    // リージョンを設定
	    client.setRegion(Region.getRegion(Regions.US_WEST_2));
	    
	    // メール送信
	    client.sendEmail(request);
		
	}
	
	private static String senderAddressBuilder(String fromAddress, String senderName) {
	    try {
	        InternetAddress address = new InternetAddress(fromAddress,
	                senderName, "ISO-2022-JP");
	        return address.toString();
	    } catch (UnsupportedEncodingException e) {
	    	System.out.println("Internet Address Constructor throws UnsupportedEncoding");
	    }
	    return fromAddress;
	}

}

文字コードは任意で設定すればいいかも
別に必須ではないかもしれません。
address.toString()である程度はいい感じに対応してくれそうです。

Spring Bootで画面からAPIへRest Templateでファイルを送信する

シンプルな使い方

jsonで送信する

難しそうな使い方

fileを送信する

やりたいこと

画面からsubmitまたはajaxでファイルを送信する。

問題点

処理の流れが
①画面でファイルを指定する
②画面でsubmitする
③Rest Templateへファイルを送信する

この場合、ファイルオブジェクトの情報をRest Templateへ渡す必要がありますが、MultipartFileをそのまま渡すことはできなかった。
別のファイルを出力したりしないといけないのかと思っていました…

Rest Templateのサンプル

Sample
package com.example.web;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import com.example.form.UploadForm;

@Controller
public class UploadController {
    
    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);
    
    @RequestMapping(value = "/upload", method = {RequestMethod.POST})
    public String upload(Model model, @ModelAttribute UploadForm form) throws IOException {
        
        logger.info(form.getName());
        logger.info(form.getUploadFile().getName());
        logger.info(form.getUploadFile().getOriginalFilename());
        logger.info(String.valueOf(form.getUploadFile().getSize()));
        
        RestTemplate restTemplate = new RestTemplate();
        
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
        
        
        ByteArrayResource contentsAsResource = new ByteArrayResource(form.getUploadFile().getBytes()){
            @Override
            public String getFilename(){
                return form.getUploadFile().getOriginalFilename();
            }
        };
        map.add("name", form.getName());
        map.add("uploadFile", contentsAsResource);
        
        HttpHeaders formHeaders = new HttpHeaders();
        formHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
        
        HttpEntity<MultiValueMap<String, Object>> formEntity = 
                new HttpEntity<MultiValueMap<String, Object>>(map, formHeaders);
        
        ResponseEntity<String> resposne =
                restTemplate.exchange("http://localhost:8080/api/upload", 
                        HttpMethod.POST, formEntity, String.class);
        
        return "/upload";
    }
    
}

MultipartFileのデータをByteArrayResourceへ再定義したものを用意して送信することでRestTemplateはファイルとして認識して送信してくれる感じ
受信データ自体はよしなに対応すればいい感じかと…

受信側

package com.example.api;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.dto.UploadDto;
import com.example.web.UploadController;

@RestController
public class ApiUploadController {
	
	private static final Logger logger = LoggerFactory.getLogger(ApiUploadController.class);
	
	@RequestMapping(value = "/api/upload", method = {RequestMethod.POST})
	public String upload(UploadDto reqDto) throws IOException {
		
		logger.info("/api/upload" + "start");
		
		logger.info(reqDto.getName());
		logger.info(reqDto.getUploadFile().getName());
		logger.info(reqDto.getUploadFile().getOriginalFilename());
		logger.info(new String(reqDto.getUploadFile().getBytes(), "UTF-8"));
		
		logger.info("/api/upload" + "end");

		return "aaaaa";
	}
	
}

Form、DTO

package com.example.form;

import java.io.Serializable;

import org.springframework.web.multipart.MultipartFile;

public class UploadForm implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String name;
	private MultipartFile uploadFile;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public MultipartFile getUploadFile() {
		return uploadFile;
	}
	public void setUploadFile(MultipartFile uploadFile) {
		this.uploadFile = uploadFile;
	}
}

所感

まあ、いろいろ探してみるもんかと思いました。
最初はできないような気がすると思っていましたがよくよく考えたらそのあたりのことを考慮していないわけがないと思いましたし…
Springは難しいですね(^^)

WatsonのVisualRecognitionに画像をURLで指定

やること

WatsonのVisualRecognitionへ画像情報を渡す際、ファイルとして送るのではなく、AWSのS3で作成した期限付きURL情報を渡して結果を取得したい。

実装

Sample1.java
import java.io.File;
import java.util.Arrays;
import java.util.List;

import com.ibm.watson.developer_cloud.visual_recognition.v3.*;
import com.ibm.watson.developer_cloud.visual_recognition.v3.model.ClassifyImagesOptions;
import com.ibm.watson.developer_cloud.visual_recognition.v3.model.VisualClassification;


public class Sample01 {
	
	private static final String API_KEY = "作成したAPIキー";
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println( "VisualRecognition classify start" );
        
        // インスタンス生成
        VisualRecognition service = new VisualRecognition(VisualRecognition.VERSION_DATE_2016_05_20);
        service.setApiKey(API_KEY);
        
        // classifierIds用
        List<String> list = Arrays.asList("分類器ID");
        
        // スコアを取得
        ClassifyImagesOptions options = new ClassifyImagesOptions.Builder()
        		.classifierIds(list)
        		.threshold(0.0)
        		.url("https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170205T071927Z&X-Amz-SignedHeaders=host&X-Amz-Expires=431989&X-Amz-Credential=AKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=f9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064")
        	    .build();
        VisualClassification result = service.classify(options).execute();
        
        // 結果を表示
        System.out.println(result);
        
        System.out.println( "VisualRecognition classify end" );
	}

}

VisualRecognition classify start
{
  "images_processed": 1,
  "images": [
    {
      "classifiers": [
        {
          "classifier_id": "dogs_605755035",
          "name": "dogs",
          "classes": [
            {
              "class": "beagle",
              "score": 0.0529675
            },
            {
              "class": "dalmation",
              "score": 0.536878
            }
          ]
        }
      ],
      "source_url": "https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type\u003dimage%2Fjpeg\u0026X-Amz-Algorithm\u003dAWS4-HMAC-SHA256\u0026X-Amz-Date\u003d20170205T071927Z\u0026X-Amz-SignedHeaders\u003dhost\u0026X-Amz-Expires\u003d431989\u0026X-Amz-Credential\u003dAKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request\u0026X-Amz-Signature\u003df9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064",
      "resolved_url": "https://awsuploadsample.s3-ap-northeast-1.amazonaws.com/dalmation_0012.jpg?response-content-type\u003dimage%2Fjpeg\u0026X-Amz-Algorithm\u003dAWS4-HMAC-SHA256\u0026X-Amz-Date\u003d20170205T071927Z\u0026X-Amz-SignedHeaders\u003dhost\u0026X-Amz-Expires\u003d431989\u0026X-Amz-Credential\u003dAKIAIEUCF6U4XJIMGKYQ%2F20170205%2Fap-northeast-1%2Fs3%2Faws4_request\u0026X-Amz-Signature\u003df9a857f597db9fc07c099aa82c7d12573d9943acd55b3b6e7343ad9cdfa36064"
    }
  ]
}
VisualRecognition classify end

できました(^^)

所感

APIでデータを送信する際、ファイルオブジェクトではなく、URL文字列でもきちんと判定してくれることがわかりました。
ローカル環境にファイルを配置できない場合などに使える感じです。

Apache Directory Studioでdcやouなどを作成する

今回やること

dc=hogehoge,dc=comみたいなものを作成する

順番

  1. パーティションを作成
  2. dcを作成
  3. ouを作成

uidを作成

パーティションを作成

「Connectiros」の「Open Configuration」を選択して設定情報を開く

f:id:m_shige1979:20170128180114p:plain

「Advanced Partitions configuration」をクリックします

f:id:m_shige1979:20170128180243p:plain

「Add」ボタンを新しいパーティション情報を追加する

f:id:m_shige1979:20170128180529p:plain

Apache DSを再起動する
sudo /etc/init.d/apacheds-2.0.0_M23-default restart

※先にApache Directory Studioの接続情報は切断しておく

再接続して確認

f:id:m_shige1979:20170128180841p:plain

組織単位(ou)の作成

作成したドメインを選択して、「New Context Entry」を選択

f:id:m_shige1979:20170128181218p:plain

「Create entry from scratch」を選択して次へ

f:id:m_shige1979:20170128181347p:plain

オブジェクトクラスを追加

f:id:m_shige1979:20170128181601p:plain

DN名を設定

f:id:m_shige1979:20170128181805p:plain

「Finish」

f:id:m_shige1979:20170128182152p:plain

作成

f:id:m_shige1979:20170128182312p:plain

ユーザーを作成

dcまたはouを選択して、「New Entry」を選択

f:id:m_shige1979:20170128182653p:plain

「Create entry from scratch」を選択する

f:id:m_shige1979:20170128182804p:plain

オブジェクトクラスを追加

f:id:m_shige1979:20170128182912p:plain

DNを設定

f:id:m_shige1979:20170128183817p:plain

必要な属性情報を設定する

f:id:m_shige1979:20170128183918p:plain

※設定情報

objectClass organizationalPerson
objectClass inetOrgPerson
objectClass person
objectClass top
cn 任意で設定
sn 任意で設定
uid 任意で設定
userPassword
作成完了

f:id:m_shige1979:20170128184030p:plain

uidではなく、cnで認証したい場合

f:id:m_shige1979:20170128184923p:plain

※設定情報

objectClass organizationalPerson
objectClass person
objectClass top
cn 任意で設定
sn 任意で設定
userPassword 任意で設定

所感

知識がないので作成するまでの調査に時間がかかりすぎました(´・ω・`)
実際はActive Directory Serverなどで設定するのでしょうが今回は同じような環境を構築したい気持ちだったのでこれで満足。
cnとかouとかの知識がちょっとずつ入っている感じです。
認証方法にも複数の方法があるようなのでその辺も調べてみる。

LDAP認証を試してみたい

LDAP認証

LDAPとは、LDAP認証とは、LDAP認証 with Cisco ASAより抜粋

LDAP(Lightweight Directory Access Protocol)は、Active Directoryのようなディレクトリサービスにアクセスするためのプロトコル

なるほど

試用してみるLDAPサーバ

Downloads for Linux RPM Package — Apache Directory
※上記よりApacheDSと管理ツールのApache DS Studioをダウンロードしてインストールする

環境

ApacheDS

Vangrantの中のCentOS7

Apache Directory Studio

Mac

Apache DSのインストール

Javaのインストール
sudo rpm -ivh jdk-8u121-linux-x64.rp

Javaを使用しているらしいのでインストールが必要

ダウンロード
curl -o apacheds-2.0.0-M23-x86_64.rpm http://ftp.meisei-u.ac.jp/mirror/apache/dist//directory/apacheds/dist/2.0.0-M23/apacheds-2.0.0-M23-x86_64.rpm
インストール
sudo rpm -ivh apacheds-2.0.0-M23-x86_64.rpm
起動
sudo /etc/init.d/apacheds-2.0.0_M23-default start

管理ツール

Apache Directory Studioを起動

f:id:m_shige1979:20170128072509p:plain

新しくコネクションを作成

f:id:m_shige1979:20170128074633p:plain

接続情報を設定

f:id:m_shige1979:20170128074649p:plain

ホスト LDAPサーバのIP
ポート 10389
ユーザーとパスワードを設定

f:id:m_shige1979:20170128074832p:plain
※テスト情報

認証 simple
ユーザーID uid=admin,ou=system
パスワード secret
完了

f:id:m_shige1979:20170128075039p:plain

画面に表示

f:id:m_shige1979:20170128075151p:plain

Javaで接続

Sample01.java
import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class Sample01 {

	public static void main(String[] args) {
		System.out.println(auth("uid=admin;ou=system", "secret"));
		System.out.println(auth("uid=admin;ou=system", "ddddddddd"));
	}
	
	public static boolean auth(String uid, String password) {
		boolean res = false;
		
		//LDAP接続情報
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, "ldap://192.168.33.10:10389/"); //LDAPサーバ
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, uid); //ID, 組織
		env.put(Context.SECURITY_CREDENTIALS, password); //パスワード
		 
		try {
		    DirContext ctx = new InitialDirContext(env);
		    ctx.close();
		    System.out.println("auth ok");
		    res = true;
		} catch (AuthenticationException ae) {
		   //認証エラー
		    System.out.println("auth ng");
		} catch (Exception e) {
		   //その他のエラー
		}
		
		return res;
	}

}

auth ok
true
auth ng
false

所感

基本、Active Directory Serviceとかよくわかっていないけどこれを機会に理解を深めていきたいと思う。
まあ、使わなかったらそれまでなんですけど…
SSLを使う方法も調べて見よう。

Spring Bootで簡易テストを行う

ぶっちゃけ

簡単じゃない
結構めんどくさい

やりたいこと

RestControllerをテストする
Serviceなどを使用している場合はモック化する

実装

Sample01Controller
package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.example.dto.sample01.Sample01ReqDto;
import com.example.dto.sample01.Sample01ResDto;
import com.example.service.Check1Service;

@RestController
public class Sample01Controller {
	
	private static final Logger logger = LoggerFactory.getLogger(Sample01Controller.class);
	
	@Autowired
	private Check1Service check1Service;
	
	@ResponseBody
	@RequestMapping(value = "/sample01/test1", method = {RequestMethod.POST}, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Sample01ResDto test1(@RequestBody Sample01ReqDto req) {
		Sample01ResDto res = new Sample01ResDto();
		
		if (check(req)) {
			res.message = "ok";
			res.status = 100L;
		} else {
			res.message = "ng";
			res.status = 900L;
		}
		
		check1Service.sample1();
		
		return res;
	}
	
	private boolean check(Sample01ReqDto req) {
		return check1Service.check(req.name);
	}
	
}

Check1Service
package com.example.service;

import org.springframework.stereotype.Service;

import com.example.dto.sample01.Sample01ReqDto;

@Service
public class Check1Service {
	
	public boolean check(String name) {
		boolean res = false;
		
		if (name != null) {
			res = true;
		}
		
		return res;
	}
	
	public void sample1() {
		String str = "1111";
		str += "222";
	}
	
}
Sample01ReqDto.java
package com.example.dto.sample01;

import java.io.Serializable;

public class Sample01ReqDto implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public String name;
	public Long age;

}
Sample01ResDto.java
package com.example.dto.sample01;

import java.io.Serializable;

public class Sample01ResDto implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public String result;
	public String message;
	public Long status;
}

実行

curl -X POST -H "Content-type: application/json" -d '{"name": null, "age": 100 }' http://localhost:8080/sample01/test1
{"result":null,"message":"ng","status":900}

テストコード

Sample01ControllerTest.java
package com.example.controller;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.hamcrest.Matchers;
import org.hamcrest.core.IsNull;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.example.dto.sample01.Sample01ReqDto;
import com.example.service.Check1Service;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class Sample01ControllerTest {
	
	@Rule
	public MockitoRule mockitoJUnitRule = MockitoJUnit.rule();
	
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));
    
    private MockMvc mockMvc;
    private HttpMessageConverter mappingJackson2HttpMessageConverter;
    
    @InjectMocks
    private Sample01Controller target;
    
    @Autowired
    private WebApplicationContext webApplicationContext;
    
    @Autowired
    void setConverters(HttpMessageConverter<?>[] converters) {

        this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream()
            .filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
            .findAny()
            .orElse(null);

        assertNotNull("the JSON message converter must not be null",
                this.mappingJackson2HttpMessageConverter);
    }
    
    @Mock
    private Check1Service check1Service;
    
    @Before
    public void setup() throws Exception {
        this.mockMvc = MockMvcBuilders.standaloneSetup(target).build();
    }
    
    /**
     * /sample01/test1
     * @throws Exception
     */
    @Test
    public void sample01Test1Post1() throws Exception {
        
        // リクエストパラメータ
        Sample01ReqDto req = new Sample01ReqDto();
        req.name = "aaaa";
        req.age = 100L;
        String jsonSample01ReqDto = json(req);
        
        // 外部クラスをmock化
        check1Service_check();
        
        // 実行
        this.mockMvc.perform(post("/sample01/test1")
                .contentType(contentType)
                .content(jsonSample01ReqDto))
                // ステータスコード
                .andExpect(status().isOk())
                // json項目のNULLチェック
                .andExpect(jsonPath("result").value(IsNull.nullValue()))
                // json項目の文字列チェック
                .andExpect(jsonPath("message", is("ok")))
                // json項目のLong型チェック
                .andExpect(jsonPath("status").value(100L))
                ;
        
    }
    
    /**
     * /sample01/test1
     * @throws Exception
     */
    @Test
    public void sample01Test1Post2() throws Exception {
        
        // リクエストパラメータ
        Sample01ReqDto req = new Sample01ReqDto();
        req.name = null;
        req.age = 200L;
        String jsonSample01ReqDto = json(req);
        
        // 外部クラスをmock化
        check1Service_check();
        
        // 実行
        this.mockMvc.perform(post("/sample01/test1")
                .contentType(contentType)
                .content(jsonSample01ReqDto))
                // ステータスコード
                .andExpect(status().isOk())
                // json項目のNULLチェック
                .andExpect(jsonPath("result").value(IsNull.nullValue()))
                // json項目の文字列チェック
                .andExpect(jsonPath("message", is("ng")))
                // json項目のLong型チェック
                .andExpect(jsonPath("status").value(900L))
                ;
    }
    
    /**
     * /sample01/test1
     * @throws Exception
     */
    @Test
    public void sample01Test1Get() throws Exception {
        
        // 実行
        this.mockMvc.perform(get("/sample01/test1")
                .contentType(contentType))
                // ステータスコード
                .andExpect(status().isMethodNotAllowed())
                ;
    }
    
    // mock化
    private void check1Service_check() {
    	// 値を設定した場合はtrue
    	when(check1Service.check(Mockito.anyString())).thenReturn(true);
    	// 値を未設定の場合はfalse
        when(check1Service.check(null)).thenReturn(false);
        
        // 戻り値がないスタブ
        doNothing().when(check1Service).sample1();
    }
        
    protected String json(Object o) throws IOException {
        MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
        this.mappingJackson2HttpMessageConverter.write(
                o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
        return mockHttpOutputMessage.getBodyAsString();
    }
    
}

やっていること

@Before
    @Before
    public void setup() throws Exception {
        this.mockMvc = MockMvcBuilders.standaloneSetup(target).build();
    }

全てのテストのメソッド前に実行されるモックオブジェクト?を作るはず
@InjectMocksしたコントローラークラスを設定することでスタブを利用できるような感じかと…

@Mock
    @Mock
    private Check1Service check1Service;

モック化したい処理をこういうふうに定義しておく
whenで実行してモックの実行結果を取得する場合に必要

@Test
    @Test
    public void sample01Test1Post1() throws Exception {
        
        // リクエストパラメータ
        Sample01ReqDto req = new Sample01ReqDto();
        req.name = "aaaa";
        req.age = 100L;
        String jsonSample01ReqDto = json(req);
        
        // 外部クラスをmock化
        check1Service_check();
        
        // 実行
        this.mockMvc.perform(post("/sample01/test1")
                .contentType(contentType)
                .content(jsonSample01ReqDto))
                // ステータスコード
                .andExpect(status().isOk())
                // json項目のNULLチェック
                .andExpect(jsonPath("result").value(IsNull.nullValue()))
                // json項目の文字列チェック
                .andExpect(jsonPath("message", is("ok")))
                // json項目のLong型チェック
                .andExpect(jsonPath("status").value(100L))
                ;
        
    }

各テスト処理を実施する。@Testアノテーションを付与したメソッドを全て実行する。

when
    // mock化
    private void check1Service_check() {
    	// 値を設定した場合はtrue
    	when(check1Service.check(Mockito.anyString())).thenReturn(true);
    	// 値を未設定の場合はfalse
        when(check1Service.check(null)).thenReturn(false);
        
        // 戻り値がないスタブ
        doNothing().when(check1Service).sample1();
    }

※スタブに引数と戻り値を設定する。引数を固定値を指定した場合はその値の結果しか返さない

f:id:m_shige1979:20170115180018p:plain

所感

テスト自体はしなくてはいけないとおもいつつも動作確認することで対応すればよいよね…
みたいな感じになるからついやらなくなってしまいます。
今回はきちんとやってみることにする画面関連のテストは無理のような感じがするけどRest APIのようにデータの流れを理解することで対応する。

まあ、あとは実装時にモック化しやすいように考慮しておく

Spring Bootでログを出力する

デバッグ中の場合とかにデータの動きを少しずつ確認したい場合など

ログを出力して確認します。
System.out.printlnではよくわからない場合など…

サンプル

Sample01Controller
package com.example.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.example.dto.sample01.Sample01ReqDto;
import com.example.dto.sample01.Sample01ResDto;

@RestController
public class Sample01Controller {
	
	private static final Logger logger = LoggerFactory.getLogger(Sample01Controller.class);
	
	@ResponseBody
	@RequestMapping(value = "/sample01/test1", method = {RequestMethod.POST}, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Sample01ResDto test1(@RequestBody Sample01ReqDto req) {
		Sample01ResDto res = new Sample01ResDto();
		
		logger.info("log sample1");
		logger.debug("log sample2");
		logger.warn("log sample3");
		logger.error("log sample4");
		
		return res;
	}
	
}

2017-01-15 16:14:58.197  INFO 35008 --- [nio-8080-exec-1] c.example.controller.Sample01Controller  : log sample1
2017-01-15 16:14:58.197  WARN 35008 --- [nio-8080-exec-1] c.example.controller.Sample01Controller  : log sample3
2017-01-15 16:14:58.197 ERROR 35008 --- [nio-8080-exec-1] c.example.controller.Sample01Controller  : log sample4

※debug以外は確認できる。おそらくなんか表示条件が違うのでしょう…

今回はとりあえずここまで…