How do I write a unit test for a Flutter method that completes later with a future? - flutter-test

I'm writing a unit test for a Flutter method that calls an async method and then returns, leaving the async to complete as and when. My test fails "after it had already completed".
Here's my test:
test('mark as viewed', () {
final a = Asset();
expect(a.viewed, false);
a.markAsViewed();
expect(a.viewed, true);
});
and here's the method it's testing:
void markAsViewed() {
viewed = true;
Repository.get().saveToStorage();
}
The saveToStorage() method is an async that I just leave to execute in the background.
How do I make this work? The test failure tells me Make sure to use [expectAsync] or the [completes] matcher when testing async code. but I can't see how to do that. Can anyone explain or else point me to the right documentation please? I can't find anything about how to handle these asyncs when it's not a Future that's being returned, but just being left to complete separately.
To be clear - this unit test isn't about testing whether it's saved to storage, just a basic test on setting viewed to be true.
Edited
The error is as follows:
package:flutter/src/services/platform_channel.dart 319:7 MethodChannel.invokeMethod
===== asynchronous gap ===========================
dart:async _asyncErrorWrapperHelper
package:exec_pointers/asset_details.dart Repository.saveToStorage
package:exec_pointers/asset_details.dart 64:22 Asset.markAsViewed
test/asset_details_test.dart 57:9 main.<fn>.<fn>
This test failed after it had already completed. Make sure to use [expectAsync]
or the [completes] matcher when testing async code.

This code is tightly coupled to implementation concerns that make testing it in isolation difficult.
It should be refactored to follow a more SOLID design with explicit dependencies that can be replaced when testing in isolation (unit testing)
For example
class Asset {
Asset({Repository repository}) {
this.repository = repository;
}
final Repository repository;
bool viewed;
void markAsViewed() {
viewed = true;
repository.saveToStorage();
}
//...
}
That way when testing a mock/stub of the dependency can be used to avoid any unwanted behavior.
// Create a Mock Repository using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockRepository extends Mock implements Repository {}
main() {
test('mark as viewed', () {
final repo = MockRepository();
// Use Mockito to do nothing when it calls the repository
when(repo.saveToStorage())
.thenAnswer((_) async => { });
final subject = Asset(repo);
expect(subject.viewed, false);
subject.markAsViewed();
expect(subject.viewed, true);
//
verify(repo.saveToStorage());
});
}
The test should now be able to be exercised without unexpected behavior from the dependency.
Reference An introduction to unit testing
Reference Mock dependencies using Mockito
Reference mockito 4.1.1

Related

Mocking is sometimes not applied when running a multi-class testsuite

I am testing a service which heavily relies on project reactor.
For many tests I am mocking the return value of the component responsible for API calls.
The tests are split over multiple files.
When I run the tests of one file, they are green, but when I execute all of the test files at once, some tests fail, with the error message indicating that the mocking did not succeed (Either the injected component returned null, or the implementation of the actual component is invoked).
In the logs, there is no information about the mocking failing.
A code example:
interface API {
Flux<Bird> getBirds();
}
#Component
class BirdWatcher {
API api;
BirdWatcher(API api) {
this.api = api;
}
Flux<Bird> getUncommonBirds() {
return api.getBirds() // Although this is mocked in the test, in some runs it returns `null` or calls the implementation of the actual component
.filter(Bird::isUncommon);
}
}
#SpringBootTest
class BirdWatcherTests {
#Autowired
BirdWatcher birdWatcher;
#MockBean
API api;
#Test
void findsUncommonBirds() {
// Assemble
Bird birdCommon = new Bird("Sparrow", "common");
Bird birdUncommon = new Bird("Parrot", "uncommon");
Mockito.when(api.getBirds()).thenReturn(Flux.just(birdCommon, birdUncommon));
// Act
Flux<Bird> uncommonBirds = birdWatcher.getUncommonBirds();
// Assert
assertThat(uncommonBirds.collectList().block().size(), equalTo(1));
}
}
For me the issue seems like a race condition, but I don't know where and how this might happen, and how I can check and fix this.
I am using spring-boot-test:2.7.8, pulling in org.mockito:mockito-core:4.5.1 org.mockito:mockito-junit-jupiter:4.5.1, and org.junit.jupiter:junit-jupiter:5.8.2, with gradle 7.8.
For reactor, spring-boot-starter-webflux:2.7.8, depending on reactor:2.7.8.

How to unit test if consumer ran successfully in MassTransit

Im trying to write a unit test that should validate if a consumer ran or not.
But for some reason the consumer never executes.
I have created a more simplified version of the problem using the Getting-Started-sample of MassTransit.
This is "my" code:
[TestClass]
public class UnitTest1
{
private ITestHarness _testHarness;
[TestInitialize]
public void Initialize()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransitTestHarness(busRegistrationConfigurator =>
{
busRegistrationConfigurator.AddConsumer<MessageConsumer>();
});
var serviceProvider = serviceCollection.BuildServiceProvider();
_testHarness = serviceProvider.GetRequiredService<ITestHarness>();
}
[TestMethod]
public async Task TestMethod1()
{
await _testHarness.Bus.Publish(new Message { Text = "Hello, world!" });
(await _testHarness.Published.Any<Message>()).Should().BeTrue();
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
}
}
The following assertion:
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
Fails, since it always returns False.
I guess that I somehow need to await the consumer to execute? And I might be missing even more..
Been having a look at the documentations of MassTransit regarding testing, but Im not sure if the sample provided in the docs apply to this scenario (?), since the sample provided involves a request and response.
Would very much appreciate any help!
You're missing one line:
await _testHarness.Start();
Of course, you'll need to be sure and Dispose the container to ensure the bus is stopped and cleaned up.

How to TDD a 2-steps process which requires input from a remote party?

I have a process which is composed by 2 steps:
(step1) first it request a token from a remote party;
(step2) second it uses the token to trigger a remote job.
TDDing (1) is pretty easy, but how to I TDD (step2)? If I write the tests as different functions I don't know where to store the token between the two tests (the DB is cleaned just before each test). If I use a fixture, (step2) will always fail because the remote job will not accept my token.
My solution up to now is to have:
test1() which (of course) tests (step1);
test2() which has the whole code of test1() plus the code for testing (step2).
While the solution work I don't like it, so I'm searching for something better.
You say that you have a process which comprises of two steps. As you have identified these discrete steps you should encapsulate them into separate methods e.g.
(Note: the following code is extremely simplistic. It would help if you included code in your question)
Instead of:
void TriggerRemoteJob()
{
// Step 1 - Code to request a token from a remote party.
// Step 2 - Code to use the token to trigger a remote job.
}
You could have:
Token GetToken(string remotePartyId)
{
return RemoteParty.GetToken(remotePartyId);
}
int TriggerRemoteJob(Token token)
{
return RemoteJobManager.TriggerJob(token);
}
"RemoteParty" and "RemoteJobManager" are both dependencies which could be injected into your class using dependency injection.
Taking this approach you could then use TDD to develop "GetToken" and "TriggerJob". When using TDD you should mock external dependencies and only test the method which you are developing. So when testing "TriggerRemoteJob" you would mock the token.
In your comment you say:
"I'd like to test the process as real as possible."
I recommend that you use a BDD approach to development (seach for BDD on StackOverflow, there are lots of great answers on this topic). This approach forces you to develop "outside-in inside-out", where you revert to using TDD as part of the BDD process. This approach will exercise all layers in your solution so you will test the process "as real as possible" whilst also writing tests (via TDD) which use mocks.
Since you are going to TDD a 2-steps process which requires input from a remote party so you definitely have to mock that remote party.
I suggest you use either MOQ or NSubstitute as mocking framework.
As I see there is no need to write two tests because two invocations are so dependent.
I'd rather write only one test to test your 2-steps process
[TestClass]
public class ClientTest
{
/// <summary>
/// This test can test only the fact of invocation only.
/// </summary>
[TestMethod]
public void DoSmth_RequestsTokenFromRemoteParty()
{
// act
target.DoSmth();
// assert
remotePartyMock.Verify(it => it.RequestTtoken());
}
/// <summary>
/// It is enough to have this test only.
/// It contains token setup and following verification of triggering job with correct tocken.
/// </summary>
[TestMethod]
public void DoSmth_RequestsTokenAndTriggersRemoteJob()
{
// arrange
var expectedTocken = Guid.NewGuid();
remotePartyMock.Setup(it => it.RequestTtoken()).Returns(expectedTocken);
// act
target.DoSmth();
// assert
remotePartyMock.Verify(it => it.TriggerRemoteJob(expectedTocken));
}
private Mock<IRemoteParty> remotePartyMock;
private Client target;
public void Init()
{
remotePartyMock = new Mock<IRemoteParty>();
target = new Client(remotePartyMock.Object);
}
}
#region Client & IRemoteParty
public class Client
{
private readonly IRemoteParty remoteParty;
/// <summary>
/// There is a constructor injection.
/// </summary>
public Client(IRemoteParty remoteParty)
{
this.remoteParty = remoteParty;
}
public void DoSmth()
{
var tocken = remoteParty.RequestTtoken();
remoteParty.TriggerRemoteJob(tocken);
}
}
public interface IRemoteParty
{
Guid RequestTtoken();
void TriggerRemoteJob(Guid token);
}
#endregion

set property on grails.test.GrailsMock

I can't seem to figure out how to set a property on a mocked Service in a Service unit test. I've tried using the demand object and the setProperty method which seems to be gone from Grails 2.
#TestFor(SomeService)
#Mock([HelperService])
class SomeServiceTests {
void testDoSomething() {
def helperService = mockFor HelperService
// tried this, error in method being tested
helperService.setProperty('propToSet',['a','b'])
// tried this, error in test
helperService.demand.propToSet = ['a','b']
// tried this, error in method being tested
helperService.demand.getPropToSet() {['a','b']}
service.helperService = helperService
assert service.doSomething('aa') != null
}
}
For most of these the error is No such property: propToSet for class: grails.test.GrailsMock, thrown from within the method I'm testing that needs it. The second option above actually gives a hard error. How do I set a property in a mocked Grails object?
I also have not-that-good experiences with Grails mocking facilities. So I've been using GMock and happy with it. GMock plays well with all Grails tests including controllers, services and domain classes as well as Spock's specifications.
To use it, you simply put the following line into grails-app/conf/BuildConfig.groovy:
dependencies {
test 'org.gmock:gmock:0.8.2'
}
And this is the GMock version of your code.
#WithGMock
#TestFor(SomeService)
class SomeServiceTests {
void testDoSomething() {
def helperService = mock(HelperService)
helperService.propToSet.returns(['a', 'b'])
service.helperService = helperService
play {
assert service.doSomething('aa') != null
}
}
}
Note that your mock codes will have affects only in the play { } block. So we need the block to wrap around assert statements.

Jpa testing and automatic rollback with Spring

I am in reference to Spring Roo In Action (book from Manning). Somewhere in the book it says "Roo marks the test class as #Transactional so that the unit tests automatically roll back any change.
Here is the illustrating method:
#Test
#Transactional
public void addAndFetchCourseViaRepo() {
Course c = new Course();
c.setCourseType(CourseTypeEnum.CONTINUING_EDUCATION);
c.setName("Stand-up Comedy");
c.setDescription(
"You'll laugh, you'll cry, it will become a part of you.");
c.setMaxiumumCapacity(10);
c.persist();
c.flush();
c.clear();
Assert.assertNotNull(c.getId());
Course c2 = Course.findCourse(c.getId());
Assert.assertNotNull(c2);
Assert.assertEquals(c.getName(), c2.getName());
Assert.assertEquals(c2.getDescription(), c.getDescription());
Assert.assertEquals(
c.getMaxiumumCapacity(), c2.getMaxiumumCapacity());
Assert.assertEquals(c.getCourseType(), c2.getCourseType());
}
However, I don't understand why changes in this method would be automatically rolled back if no RuntimeException occurs...
Quote from documentation:
By default, the framework will create and roll back a transaction for each test. You simply write code that can assume the existence of a transaction. [...] In addition, if test methods delete the contents of selected tables while running within a transaction, the transaction will roll back by default, and the database will return to its state prior to execution of the test. Transactional support is provided to your test class via a PlatformTransactionManager bean defined in the test's application context.
So, in other words, SpringJUnit4ClassRunner who runs your tests always do transaction rollback after test execution.
I'm trying to find a method that allows me to do a rollback when one of the elements of a list fails for a reason within the business rules established (ie: when throw my customize exception)
Example, (the idea is not recording anything if one element in list fails)
public class ControlSaveElement {
public void saveRecords(List<MyRecord> listRecords) {
Boolean status = true;
foreach(MyRecord element: listRecords) {
// Here is business rules
if(element.getStatus() == false) {
// something
status = false;
}
element.persist();
}
if(status == false) {
// I need to do roll back from all elements persisted before
}
}
...
}
Any idea? I'm working with Roo 1.2.2..

Resources