본문 바로가기

Flutter

[Flutter] 채팅앱 만들기 #2

What to do?

 MessageService를 만들고, Test 코드 작성하기


Reference


Pubspec.yaml

 

로깅을 하기 위해 라이브러리 설치

dependencies:
  flutter:
    sdk: flutter
  ...
  logger: ^1.2.2

pubspec.yaml


Message Model

 

/model/message_model.dart

  • from : 메세지를 보낸 유저의 id
  • to : 메세지를 받는 유저의 id
class Message {
  String get id => _id;
  String _id;

  /// from : sender id
  /// to : receiver id
  final String from;
  final String to;
  final DateTime timestamp;
  final String contents;

  Message(
      {@required this.from,
      @required this.to,
      @required this.timestamp,
      @required this.contents});

  toJson() =>
      {'from': from, 'to': to, 'timestamp': timestamp, 'contents': contents};

  factory Message.fromJson(Map<String, dynamic> json) {
    var message = Message(
        from: json['from'],
        to: json['to'],
        timestamp: json['timestamp'],
        contents: json['contents']);
    message._id = json['id'];
    return message;
  }
}

MessageService

 

/service/message_service_interface.dart

  • message service에서 구현해야 하는 메써드는 send, messages, dispose
  • send : 메세지 보내기
  • messages : 특정 유저가 받은 메세지들을 반환
  • dispose : 접속 종료 
abstract class IMessageService {
  Future<bool> send(Message message);

  Stream<Message> messages({@required User user});

  dispose();
}

 

/service/user_serivce.dart

class MessageService implements IMessageService {
  final Connection _connection;
  final Rethinkdb _db;
   
  // 에러 발생시 로깅
  var _logger = Logger();

  final _controller = StreamController<Message>.broadcast();
  StreamSubscription _changefeed;

  MessageService(this._db, this._connection);

  @override
  dispose() {
    // 구독 취소
    _changefeed?.cancel();
    _controller?.close();
  }

  @override
  Stream<Message> messages({User user}) {
    _startReceivingMessages(user);
    return _controller.stream;
  }

  @override
  Future<bool> send(Message message) async {
    // Message를 받아서 데이터베이스에 저장
    Map record =
    await _db.table('messages').insert(message.toJson()).run(_connection);
    // DB에 데이터가 잘 들어갔는지 여부를 반환
    return record['inserted'] == 1;
  }

  _startReceivingMessages(User user) {
    _changefeed = _db
        .table('messages')
        .filter({'to': user.id})
        .changes({'include_initial': true})
        .run(_connection)
        .asStream()
        .cast<Feed>()
        .listen((event) {
      event
          .forEach((feedData) {
        if (feedData['new_val'] == null) return;

        final message = _messageFromFeed(feedData);
        _controller.sink.add(message);
        _removeDeliveredMessage(message);
      })
          .catchError((err) => _logger.e(err))
          .onError((err, stackTrace) => _logger.e(err));
    });
  }

  Message _messageFromFeed(feedData) {
    return Message.fromJson(feedData['new_val']);
  }

  _removeDeliveredMessage(Message message) {
    _db
        .table('messages')
        .get(message.id)
        .delete({'return_changes': false}).run(_connection);
  }
}

Test 코드

 

test/util_for_test.dart

  • 테스트 시작 시에 △ test라는 데이터 베이스 생성 △ users 테이블 생성  △ messages 테이블 생성 
  • 테스트 종료 시에  △ users 테이블 삭제  △ messages 테이블 삭제
Future<void> createDatabase(Rethinkdb db, Connection connection) async {
  await db.dbCreate('test').run(connection).catchError((err) => {});
  await db.tableCreate('users').run(connection).catchError((err) => {});
  await db.tableCreate('messages').run(connection).catchError((err) => {});
}

Future<void> cleanDatabase(Rethinkdb db, Connection connection) async {
  await db.table('users').delete().run(connection);
  await db.table('messages').delete().run(connection);
}

 

test/message_service_test.dart

void main() {
  Rethinkdb _db = Rethinkdb();
  Connection _connection;
  MessageService sut;

  setUp(() async {
    _connection = await _db.connect(host: '127.0.0.1', port: 28015);
    await createDatabase(_db, _connection);
    sut = MessageService(_db, _connection);
  });

  tearDown(() async {
    sut.dispose();
    await cleanDatabase(_db, _connection);
  });

  final sender =
      User.fromJson({'id': '1', 'active': true, 'lastSeen': DateTime.now()});

  final receiver =
      User.fromJson({'id': '2', 'active': true, 'lastSeen': DateTime.now()});

  test('if message sent successfully, then return true', () async {
    Message message = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    final result = await sut.send(message);
    expect(result, true);
  });

  test('sending message works successfully', () async {
    sut.messages(user: receiver).listen(expectAsync1((message) {
          expect(message.to, receiver.id);
          expect(message.id, isNotEmpty);
        }, count: 2));
    Message firstMessage = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    Message secondMessage = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    await sut.send(firstMessage);
    await sut.send(secondMessage);
  });

  test('successfully subscribe and receive messages', () async {
    sut.messages(user: receiver).listen(expectAsync1((message) {
          expect(message.to, receiver.id);
          expect(message.id, isNotEmpty);
        }, count: 2));

    Message message = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is a message',
    );

    Message secondMessage = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is another message',
    );

    await sut.send(message);
    await sut.send(secondMessage);
  });

  test('successfully subscribe and receive new messages ', () async {
    Message message = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is a message',
    );

    Message secondMessage = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is another message',
    );

    await sut.send(message);
    await sut.send(secondMessage).whenComplete(
          () => sut.messages(user: receiver).listen(
                expectAsync1((message) {
                  expect(message.to, receiver.id);
                }, count: 2),
              ),
        );
  });
}

테스트 실행

 

docker 컨테이너 실행

docker run -d -p 8080:8080 -p 20815:20815 rethinkdb

 

테스트 코드 실행

테스트 성공

 

'Flutter' 카테고리의 다른 글